您的位置:首页 > 其它

算法导论例程——红黑树

2016-02-14 00:57 381 查看
红黑树是比较重要的数据结构,作为一个典型的平衡二叉树(没有一条简单路径是其他路径的二倍)。它与二叉搜索树的区别是它的结点多了一个属性——color,color有两种值,red和black,颜色的选取遵循以下原则:

1)每个节点是红色的,或是黑色的;

2)根节点是黑色的;(松弛红黑树 relaxed red-black tree的根节点可以是红色)

3)每个叶节点(NIL)是黑色的;

4)如果一个节点是红色的,那么他的两个子节点都是黑色的;

5)对每个节点,从该节点到其所有后代节点的简单路径上,均包含相同数目的黑色节点;

#define BLACK 0;
#define RED 1;
using namespace std;
typedef struct red_black_tree
{
int num;
red_black_tree* p;
red_black_tree* left;
red_black_tree* right;
int color;
}RBT;


可见性质中有很多都是用黑节点来定义的,所以为了描述方便还引进了一个概念——黑高bh(x),指的是从节点x到叶节点的任一简单路径的黑节点的数目。

由此可以推得,以x为根的子树至少包含2^(bh(x)) - 1个内部节点,推广到整棵树,得到有n个节点的红黑树其高度不超过2lg(n+1)。

搜索树的操作insert和delete在红黑树上的最坏运行时间为o(lgn),由于这两种操作对树的结构做出了改变,我们需要一个机制来维护红黑树的性质,由于红黑树是有color属性的,所以简单的trans操作不适用,我们引进新的基本操作——旋转。

红黑树的旋转分为左旋和右旋,以左旋为例:对节点x的左旋,假设x有右孩子y,那么旋转之后x成为y的左孩子,y的左孩子成为x的右孩子,y代替了x的位置。这样做不会改变二叉搜索树的性质。

要提示一点的是,红黑树中不允许出现“没有颜色”的空节点,因此本节代码中所有的NULL都应替换为以下声明的一个NIL哨兵节点,它是一种哑元节点,对速度没有什么影响。

RBT NIL;
{
NIL.left = NULL;
NIL.right = NULL;
NIL.color = BLACK;
}


void left_rotate(RBT* root, RBT* key)
{
RBT* key_r;
if (key->right != NULL)
key_r = key->right;
else
return -1;                       //没有右孩子无法左旋

if(key_r->left != NULL)
key_r->left->p = key;
key->right = key_r->left;

if (key->p == NULL)
{
key_r->p = NULL;
root = key_r;
}
else if (key == key->p->left)
key->p->left = key_r;
else
key->p->right = key_r;
key_r->p = key->p;

key_r->left = key;
key->p = key_r;
}


void rigth_rotate(RBT* root, RBT* key)
{
RBT* key_l;
if (key->left != NULL)
key_l = key->left;
else
return -1;                       //没有左孩子无法右旋

if (key_l->right != NULL)
key_l->right->p = key;
key->left = key_l->right;

if (key->p == NULL)
{
key_l->p = NULL;
root = key_l;
}
else if (key == key->p->left)
key->p->left = key_l;
else
key->p->right = key_l;
key_l->p = key->p;

key_l->right = key;
key->p = key_l;
}


有了左旋和右旋作为基础,我们就可以考虑对红红黑树的插入操作了。首先我们要明确的一点是——红黑树依然是二叉搜索树,因此我们可以仿照二叉搜索树的插入方法来写出 红黑树的插入,这样可以保证我们的插入操作不会使树违反二叉搜索树的性质。

void RB_insert(RBT* root, RBT* key)
{
RBT *x, *y;
x = root;
while (x != NULL)
{
y = x;
if (key->num < x->num)
x = x->left;
else
x = x->right;
}
key->p = y;
if (y == NULL)
root = key;
else if (key->num < y->num)
y->left = key;
else
y->right = key;

key->left = NULL;
key->right = NULL;
key->color = RED;
RB_insert_fix(root, key);
}


唯一的不同就是我们把新插入的节点着色为红色,之所以着为红色是因为这样只会使它违反红黑树的性质2和性质4,方便我们后期的维护。

接下来呢我们讨论关键字节点的父节点、祖父节点和叔叔节点的颜色对其进行讨论,其中case2和case3是具有关联性的,case2完成旋转之后一定会成为case3。具体的讨论参见书,这里就不画图了。

case1: key的叔叔节点是红色

case2:key的叔叔节点是黑色的且key是一个右孩子

case3:key的叔叔节点是黑色且key是一个左孩子

void RB_insert_fix(RBT* root, RBT* key)
{
RBT *y;
while (key->p->color == RED)
{
if (key->p == key->p->p->left)
{
y = key->p->p->right;                           //uncle node
if (y->color == RED)
{
key->p->color = BLACK;
y->color = BLACK;
key->p->p->color = RED;
key = key->p->p;
//case 1:  father node is RED,uncle node is RED
}
else if (key == key->p->right)
{
key = key->p;
left_rotate(root, key);
//case 2: father node is RED,but uncle node is black
}
key->p->color = BLACK;
key->p->p->color = RED;
right_rotate(root, key->p->p);
//case 3: father node is RED,uncle node is BLACK,key is the left child
}
else
{
y = key->p->p->left;
if (y->color == RED)
{
key->p->color = BLACK;
y->color = BLACK;
key->p->p->color = RED;
key = key->p->p;
}
else if (key == key->p->left)
{
key = key->p;
right_rotate(root, key);
}
key->p->color = BLACK;
key->p->p->color = RED;
left_rotate(root, key->p->p);
}
}
root->color = BLACK;
}


红黑树放弃了追求完全平衡,追求大致平衡,在与平衡二叉树的时间复杂度相差不大的情况下,保证每次插入最多只需要三次旋转就能达到平衡,实现起来也更为简单。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: