彻底搞懂红黑树

首先是红黑树

零  八卦起源

    1972年,鲁道夫贝尔最先发明,但是他称之为“对称二叉B树”,真正的称之为“红黑树”是在1978年Leo J. Guibas 和 Robert Sedgewick的一篇论文开始的。
红黑树的命名和Xerox PARC也有关系,当时Robert Sedgewick在施乐做访问学者的时候,施乐正好发明出激光彩色打印机,然后觉得这个打印机打出的红色很好看,就用了红色来区分结点的不同,于是就叫红黑树了。 (注:这个数据结构是1972 由 Rudolf Bayer 发明的,叫 "symmetric binary B-tree")。摘自左耳朵耗子的 微博。


一  红黑树的定义

算法导论里是这样定义一棵红黑树的:
      1、每个结点或是红色的,或是黑色的
      2、根节点是黑色的
      3、每个叶结点(NIL)是黑色的
      4、如果一个节点是红色的,则它的两个儿子都是黑色的。
      5、对于每个结点,从该结点到其子孙结点的所有路径上包含相同数目的黑色结点。
    话说这些性质里面,前四个都很容易理解,但惟独第五个,我是怎么读怎么觉得拗口,原因就是对这个“子孙节点”感到疑惑,这个子孙节点是啥子玩意儿呢?所有的子、孙节点??? 如果当前节点是根节点,那么根据定义:子孙节点有相同的黑色节点数,那不是搞笑吗?怎么可能得到呢。后来我看了一下英文版的,发现是这个:descendant leaves,貌似理解为子孙节点也不错哈,但我查了《Introduction to Algorithms》全书,也就这么一处用了descendant leaves,其他地方都是leaves,很显然这里的descendant leaves应该是特指,不应该就泛泛的翻译为子孙节点,而应该是子孙叶节点才对,也就是树最边上的节点啦。这样理解起来也自然,也不会有歧义。然后对照红黑树的图(下图一)一比较,果然是这样。也不晓得是不是自己的语文差了,反正这里顺便鄙视下《算法导论》的翻译吧。也就是说从根节点(如下图 13根节点)到其所有的nil黑节点路径里的黑节点数就是一样的。


图一
    好吧,红黑树的性质就理解到这里。也许大家看书的话就会继续往下看了,当然我也是这样的。但是当我读第二遍的时候,我就想为什么这简简单单的5条性质就能让红黑树有如此好的性质。AVL树我还可以理解,毕竟有看得见摸得着的定义和balance代码,但红黑树我就不理解了,因为他的定义是如此的简单,也不可能看到定义就能想象它是如何调整树结构达到动态balance的,搞得我很恼火。我曾经试图搜索下 红黑树本质,但发现没有这方面的信息。o(╯□╰)o,我也只好自己思考下了。


二  引理

    估计有很多人对书上13.1 引理不是很在意,但是我想说,这正是理解红黑树精髓的地方之一。 这条引理也是红黑树为什么效率这么高的原因。不晓得是语文差还是啥的,我看书上的介绍也没看懂,写得太简洁了,菜鸟看不懂表示很蛋疼。后来在网上看了别人的资料,终于弄懂了。(⊙o⊙)…
 
下面我就仔仔细细的介绍下这条引理到底是怎么得到的。
引理:一棵有n个内结点的红黑树的高度至多为2lg(n+1)。
    这个引理怎么证明呢,这里需要一个工具  对以x为根的子树,它所包含的内部结点数至少为2^[bh(x)]-1。这里bh(x)(bh嘛,black height)被定义为结点x的黑高度,就是说,从结点x(不包括它本身)到它任一个叶结点的路径上所有黑色结点的个数。 
下面用归纳法证明:
    1)若x高度为0,那么它就是一叶子结点,它确实至少包含2^0-1=0个内部结点

    2)假设x为红黑树的某一内部结点,且它高度h>0,那么它的黑高度就是bh(x),但是它的两个孩子结点呢?这个就根据它们的颜色来判断了:
       如果x有一个红色的孩子y,那么y的黑高度bh(y)=bh(x),看看上面对黑高度的定义你就明白了——既然它是红色的,那么它的黑高度就应该和它父亲的黑高度是一样的;
       如果x有一个黑色的孩子z,那么z的黑高度bh(z)=bh(x)-1,这个怎么解释呢,因为它自己就是个黑结点,那么在计算它的黑高度时,必须把它自己排除在外(还是根据定义),所以它是bh(x)-1。

    3)x的孩子结点所构成的子树的高度肯定小于x这颗子树,那么对于这两个孩子,不管它们颜色如何,一定满足归纳假设的是至少hb 高度为bh(x)-1。所以,对x来说,它所包含的内部结点个数“至少”为两个孩子结点所包含的内部结点数,再加上它自己,于是就为2^[bh(x)-1]-1+2^[bh(x)-1]-1+1=2^[bh(x)]-1,归纳证明完毕。
也就是说n>=2^[bh(x)]-1---------①

     把一 中红黑树性质中 4)、5)两个特性结合起来,其实我们可以得到黑节点至少是红节点的2倍。用一句话来说就是“有红必有黑,但有黑未必一定有红”。为什么这么说呢,因为从特性4)我们知道,如果有一个红结点存在,那么它的儿子结点一定是黑的,最极端的情况下,该路径上所有的结点就被红、黑两种结点给平分了那就是黑节点至少是红节点的2倍。不知这个问题我解释清楚没有,因为这是往下理解的关键。
 
    如果一棵红黑树的高为h,那么在这个高度上(不包括根结点本身)至少有1/2h的黑结点,再结合上面对“黑高度”的定义,我们说,红黑树根结点的黑高度至少是1/2h,好了,我们拿出公式①,设n为该红黑树所包含的内部结点数,我们得出如下结论: n>=2^(1/2h)-1。 我们把它整理整理,就得到了h<=2lg(n+1),就是我们要证明的结论:红黑树的高度最多也就是2lg(n+1)。(⊙o⊙)

其实关于红黑树,STL源码剖析---红黑树原理详解 已经写得非常好了。但套用新警察故事里的谢霆锋说的一句话:自己查,印象深一点。这里也是一样,在自己写,印象深一点。如果你要看正宗的STL源码剖析---红黑树原理详解,那请你点击这个。这里的是D版的o(╯□╰)o  当然,我也会加一些我自己的理解,因为大神写文章都比较精简,而我这是写给我自己看的,有一点口水话加深点印象。

三 红黑树的插入


红黑树的节点插入默认是节点为红色的。我自己理解是,其实插入红还是黑都可以,但就要看后面的调整是否麻烦。

插入黑点,会增加路径上黑点的数目,一定会破坏性质5
插入红点:

当其父节点为黑色时,不影响平衡,继续保持红黑性质
当其父节点为红色时,可能破坏性质2(根节点是黑色的)、性质4(红色节点的子节点一定是黑色节点),需要进行修正。

因为第一篇文章已经说了,黑节点至少是红节点的两倍,这说明插入红节点OK的概率高很多(因为父节点为黑,插入子节点为红色就不会和性质冲突),这样插入就省事多了,嘿嘿,这也是红黑树为什么战胜AVL树的原因之一,插入效率高啊,节点贴上去就ok了,都不用什么左转右转调整了,多省事啊;再说,如果插入子节点为黑色,o(╯□╰)o了,黑高度变化了,得调整,如果每次都插入黑节点,都得调整,没事闲的蛋疼啊。。。


红黑树插入分一下几种情况:

1、黑父

   如下图所示,如果新节点的父结点为黑色结点,那么插入一个红点将不会影响红黑树的平衡,此时插入操作完成。红黑树比AVL树优秀的地方之一,从而使红黑树需要旋转在于黑父的情况比较常见的几率相对AVL树来说会少一些。(大神和我的理解差不多,不过别人的精简很多,我的就是口水话。)

2、红父
     如果新节点的父结点为红色,这时就需要进行一系列操作以保证整棵树红黑性质。如下图所示,由于父结点为红色,此时可以判定,祖父结点必定为黑色。这时需要根据叔父结点的颜色来决定做什么样的操作。青色结点表示颜色未知。由于有可能需要根结点到新点的路径上进行多次旋转操作,而每次进行不平衡判断的起始点(我们可将其视为新点)都不一样。所以我们在此使用一个蓝色箭头指向这个起始点,并称之为判定点。
图一
2.1 红叔
当叔父结点为红色时,如下图所示,无需进行旋转操作,只要将父和叔结点变为黑色,将祖父结点变为红色即可。但由于祖父结点的父结点有可能为红色,从而违反红黑树性质。此时必须将祖父结点作为新的判定点继续向上(迭代)进行平衡操作。(注意这里是需要迭代的,有可能会调整到根节点)

图二
需要注意的是,无论“父节点”在“叔节点”的左边还是右边,无论“新节点”是“父节点”的左孩子还是右孩子,它们的操作都是完全一样的(其实这种情况包括4种,只需调整颜色,不需要旋转树形)。


2.2 黑叔
当叔父结点为黑色时,需要进行旋转,以下图示了所有的旋转可能:(case1 和caes 2 都是把左边较大的节点调整到上面去

Case 1:


图三

Case 2:


图四

其实这里case 2的图示进行了简化,就是case 2 需要L变换成case 1 的情形如然后再进行R旋转,得到最后的结果。 当然,下面的也一样



图五(图中省略了哨兵结点



Case 3:

Case 4:


      可以观察到,当旋转完成后,新的旋转根全部为黑色,此时不需要再向上回溯进行平衡操作,插入操作完成。需要注意,上面四张图的“叔”、“1”、“2”、“3”结点有可能为黑哨兵结点。

      有了上面的分析,红黑树的插入就好理解多了,代码也容易读懂。不过不是很喜欢c++的代码,有时间了把linux内核的红黑树源码贴出来。这里还是贴的大神注视的红黑树的插入操作源代码:

stl里的红黑树插入操作源码

[cpp]  view plain  copy
  1. // 元素插入操作  insert_unique()  
  2. // 插入新值:节点键值不允许重复,若重复则插入无效  
  3. // 注意,返回值是个pair,第一个元素是个红黑树迭代器,指向新增节点  
  4. // 第二个元素表示插入成功与否  
  5. template<class Key , class Value , class KeyOfValue , class Compare , class Alloc>  
  6. pair<typename rb_tree<Key , Value , KeyOfValue , Compare , Alloc>::iterator , bool>  
  7. rb_tree<Key , Value , KeyOfValue , Compare , Alloc>::insert_unique(const Value &v)  
  8. {  
  9.     rb_tree_node* y = header;    // 根节点root的父节点  
  10.     rb_tree_node* x = root();    // 从根节点开始  
  11.     bool comp = true;  
  12.     while(x != 0)  
  13.     {  
  14.         y = x;  
  15.         comp = key_compare(KeyOfValue()(v) , key(x));    // v键值小于目前节点之键值?  
  16.         x = comp ? left(x) : right(x);   // 遇“大”则往左,遇“小于或等于”则往右  
  17.     }  
  18.     // 离开while循环之后,y所指即插入点之父节点(此时的它必为叶节点)  
  19.     iterator j = iterator(y);     // 令迭代器j指向插入点之父节点y  
  20.     if(comp)     // 如果离开while循环时comp为真(表示遇“大”,将插入于左侧)  
  21.     {  
  22.         if(j == begin())    // 如果插入点之父节点为最左节点  
  23.             return pair<iterator , bool>(_insert(x , y , z) , true);  
  24.         else     // 否则(插入点之父节点不为最左节点)  
  25.             --j;   // 调整j,回头准备测试  
  26.     }  
  27.     if(key_compare(key(j.node) , KeyOfValue()(v) ))  
  28.         // 新键值不与既有节点之键值重复,于是以下执行安插操作  
  29.         return pair<iterator , bool>(_insert(x , y , z) , true);  
  30.     // 以上,x为新值插入点,y为插入点之父节点,v为新值  
  31.   
  32.   
  33.     // 进行至此,表示新值一定与树中键值重复,那么就不应该插入新值  
  34.     return pair<iterator , bool>(j , false);  
  35. }  
  36.   
  37.   
  38. // 真正地插入执行程序 _insert()  
  39. template<class Key , class Value , class KeyOfValue , class Compare , class Alloc>  
  40. typename<Key , Value , KeyOfValue , Compare , Alloc>::_insert(base_ptr x_ , base_ptr y_ , const Value &v)  
  41. {  
  42.     // 参数x_ 为新值插入点,参数y_为插入点之父节点,参数v为新值  
  43.     link_type x = (link_type) x_;  
  44.     link_type y = (link_type) y_;  
  45.     link_type z;  
  46.   
  47.   
  48.     // key_compare 是键值大小比较准则。应该会是个function object  
  49.     if(y == header || x != 0 || key_compare(KeyOfValue()(v) , key(y) ))  
  50.     {  
  51.         z = create_node(v);    // 产生一个新节点  
  52.         left(y) = z;           // 这使得当y即为header时,leftmost() = z  
  53.         if(y == header)  
  54.         {  
  55.             root() = z;  
  56.             rightmost() = z;  
  57.         }  
  58.         else if(y == leftmost())     // 如果y为最左节点  
  59.             leftmost() = z;          // 维护leftmost(),使它永远指向最左节点  
  60.     }  
  61.     else  
  62.     {  
  63.         z = create_node(v);        // 产生一个新节点  
  64.         right(y) = z;              // 令新节点成为插入点之父节点y的右子节点  
  65.         if(y == rightmost())  
  66.             rightmost() = z;       // 维护rightmost(),使它永远指向最右节点  
  67.     }  
  68.     parent(z) = y;      // 设定新节点的父节点  
  69.     left(z) = 0;        // 设定新节点的左子节点  
  70.     right(z) = 0;       // 设定新节点的右子节点  
  71.     // 新节点的颜色将在_rb_tree_rebalance()设定(并调整)  
  72.     _rb_tree_rebalance(z , header->parent);      // 参数一为新增节点,参数二为根节点root  
  73.     ++node_count;       // 节点数累加  
  74.     return iterator(z);  // 返回一个迭代器,指向新增节点  
  75. }  
  76.   
  77.   
  78.   
  79.   
  80. // 全局函数  
  81. // 重新令树形平衡(改变颜色及旋转树形)  
  82. // 参数一为新增节点,参数二为根节点root  
  83. inline void _rb_tree_rebalance(_rb_tree_node_base* x , _rb_tree_node_base*& root)  
  84. {  
  85.     x->color = _rb_tree_red;    //新节点必为红  
  86.     while(x != root && x->parent->color == _rb_tree_red)    // 父节点为红  
  87.     {  
  88.         if(x->parent == x->parent->parent->left)      // 父节点为祖父节点之左子节点  
  89.         {  
  90.             _rb_tree_node_base* y = x->parent->parent->right;    // 令y为伯父节点  
  91.             if(y && y->color == _rb_tree_red)    // 伯父节点存在,且为红  
  92.             {  
  93.                 x->parent->color = _rb_tree_black;           // 更改父节点为黑色  
  94.                 y->color = _rb_tree_black;                   // 更改伯父节点为黑色  
  95.                 x->parent->parent->color = _rb_tree_red;     // 更改祖父节点为红色  
  96.                 x = x->parent->parent;  
  97.             }  
  98.             else    // 无伯父节点,或伯父节点为黑色  
  99.             {  
  100.                 if(x == x->parent->right)   // 如果新节点为父节点之右子节点  
  101.                 {  
  102.                     x = x->parent;  
  103.                     _rb_tree_rotate_left(x , root);    // 第一个参数为左旋点  
  104.                 }  
  105.                 x->parent->color = _rb_tree_black;     // 改变颜色  
  106.                 x->parent->parent->color = _rb_tree_red;  
  107.                 _rb_tree_rotate_right(x->parent->parent , root);    // 第一个参数为右旋点  
  108.             }  
  109.         }  
  110.         else          // 父节点为祖父节点之右子节点  
  111.         {  
  112.             _rb_tree_node_base* y = x->parent->parent->left;    // 令y为伯父节点  
  113.             if(y && y->color == _rb_tree_red)    // 有伯父节点,且为红  
  114.             {  
  115.                 x->parent->color = _rb_tree_black;           // 更改父节点为黑色  
  116.                 y->color = _rb_tree_black;                   // 更改伯父节点为黑色  
  117.                 x->parent->parent->color = _rb_tree_red;     // 更改祖父节点为红色  
  118.                 x = x->parent->parent;          // 准备继续往上层检查  
  119.             }  
  120.             else    // 无伯父节点,或伯父节点为黑色  
  121.             {  
  122.                 if(x == x->parent->left)        // 如果新节点为父节点之左子节点  
  123.                 {  
  124.                     x = x->parent;  
  125.                     _rb_tree_rotate_right(x , root);    // 第一个参数为右旋点  
  126.                 }  
  127.                 x->parent->color = _rb_tree_black;     // 改变颜色  
  128.                 x->parent->parent->color = _rb_tree_red;  
  129.                 _rb_tree_rotate_left(x->parent->parent , root);    // 第一个参数为左旋点  
  130.             }  
  131.         }  
  132.     }//while  
  133.     root->color = _rb_tree_black;    // 根节点永远为黑色  
  134. }  
  135.   
  136.   
  137.   
  138.   
  139. // 左旋函数  
  140. inline void _rb_tree_rotate_left(_rb_tree_node_base* x , _rb_tree_node_base*& root)  
  141. {  
  142.     // x 为旋转点  
  143.     _rb_tree_node_base* y = x->right;          // 令y为旋转点的右子节点  
  144.     x->right = y->left;  
  145.     if(y->left != 0)  
  146.         y->left->parent = x;           // 别忘了回马枪设定父节点  
  147.     y->parent = x->parent;  
  148.   
  149.   
  150.     // 令y完全顶替x的地位(必须将x对其父节点的关系完全接收过来)  
  151.     if(x == root)    // x为根节点  
  152.         root = y;  
  153.     else if(x == x->parent->left)         // x为其父节点的左子节点  
  154.         x->parent->left = y;  
  155.     else                                  // x为其父节点的右子节点  
  156.         x->parent->right = y;  
  157.     y->left = x;  
  158.     x->parent = y;  
  159. }  
  160.   
  161.   
  162.   
  163.   
  164. // 右旋函数  
  165. inline void _rb_tree_rotate_right(_rb_tree_node_base* x , _rb_tree_node_base*& root)  
  166. {  
  167.     // x 为旋转点  
  168.     _rb_tree_node_base* y = x->left;          // 令y为旋转点的左子节点  
  169.     x->left = y->right;  
  170.     if(y->right != 0)  
  171.         y->right->parent = x;           // 别忘了回马枪设定父节点  
  172.     y->parent = x->parent;  
  173.   
  174.   
  175.     // 令y完全顶替x的地位(必须将x对其父节点的关系完全接收过来)  
  176.     if(x == root)  
  177.         root = y;  
  178.     else if(x == x->parent->right)         // x为其父节点的右子节点  
  179.         x->parent->right = y;  
  180.     else                                  // x为其父节点的左子节点  
  181.         x->parent->left = y;  
  182.     y->right = x;  
  183.     x->parent = y;  
  184. }  


linux 源码里的红黑树插入

自认为好读多了 O(∩_∩)O~

[cpp]  view plain  copy
  1. /** 
  2.  * node是新插入的结点,有着默认的红色。 
  3.  * 本函数检查是否有违背红黑树性质的地方,并进行纠正。 
  4.  */  
  5. void rb_insert_color(rb_node *node, rb_root *root) {  
  6.     rb_node *parent, *gp;  
  7.   
  8.     /** 
  9.      * 如果有父节点且父节点是红色,进行树的调整以保证树的性质。 
  10.      */  
  11.     while ((parent = rb_parent(node)) && rb_is_red(parent)) {  
  12.         gp = rb_parent(parent);  
  13.   
  14.         if (parent == gp->left) {  
  15.             /** 
  16.              * 父节点是祖父节点左子的情况。 
  17.              * "else"中的情况左右相反,这里只注释"if"里的代码。 
  18.              */  
  19.             register rb_node *uncle = gp->right;  
  20.             if (uncle && rb_is_red(uncle)) {       <span style="color:#ff0000;">//对应图一</span>  
  21.                 /** 
  22.                  * 如果有红叔,则将红叔与红父均涂黑,并将祖父节点涂红。 
  23.                  */  
  24.                 rb_set_black(parent);  
  25.                 rb_set_black(uncle);  
  26.                 rb_set_red(gp);  
  27.                 /** 
  28.                  * 现在简单路径中黑节点个数仍然平衡,但祖父变成了红色, 
  29.                  * 我们不确定有没有造成父子均红的情况,所以需要对祖父节点进行下一轮修复。 
  30.                  */  
  31.                 node = gp;  <span style="color:#ff0000;">//这里要递归fix</span>  
  32.                 continue;  
  33.             }  
  34.   
  35.             /** 
  36.              * 现在是叔节点为空或为黑的情况。 
  37.              */  
  38.             if (node == parent->right) {     
  39.                 /** 
  40.                  * 如果新节点是父节点的右子,对父节点进行左旋。 
  41.                  * 旋转后树仍然平衡,但新节点占了原父节点的位子。 
  42.                  * 这两个节点交换角色后,新的父节点是红的,其左子也是红的。 
  43.                  */  
  44.                 _rb_rotate_left(parent, root);   <span style="color:#ff0000;"//对应图四-》图五</span>  
  45.                 register rb_node *tmp = node;  
  46.                 node = parent;  
  47.                 parent = tmp;  
  48.             }  
  49.             /** 
  50.              * 此时父红左子红,树平衡但有连续红节点。 
  51.              * 
  52.              * 父涂黑,祖父涂红,再对祖父右旋,树即调整到合法状态。 
  53.              */  
  54.             rb_set_black(parent);     <span style="color:#ff0000;">//对应图三</span>  
  55.             rb_set_red(gp);  
  56.             _rb_rotate_right(gp, root);  
  57.             return;  
  58.         } else {  
  59.             register rb_node *uncle = gp->left;  
  60.             if (uncle && rb_is_red(uncle)) {  
  61.                 rb_set_black(parent);  
  62.                 rb_set_black(uncle);  
  63.                 rb_set_red(gp);  
  64.                 node = gp;  
  65.                 continue;  
  66.             }  
  67.   
  68.             if (node == parent->left) {  
  69.                 _rb_rotate_right(parent, root);  
  70.                 register rb_node *tmp = node;  
  71.                 node = parent;  
  72.                 parent = tmp;  
  73.             }  
  74.             rb_set_black(parent);  
  75.             rb_set_red(gp);  
  76.             _rb_rotate_left(gp, root);  
  77.             return;  
  78.         }  
  79.     }  
  80.     /** 
  81.      * 若无父节点,只需将node涂黑; 
  82.      * 若父节点为黑,插入红节点不影响树的性质。 
  83.      * 循环体后直接将node涂黑,可以同时保证以上两点。 
  84.      */  
  85.      rb_set_black(root->node);  
  86. }  

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值