首先是红黑树
零 八卦起源
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里的红黑树插入操作源码
template < class Key , class Value , class KeyOfValue , class Compare , class Alloc> pair<typename rb_tree<Key , Value , KeyOfValue , Compare , Alloc>::iterator , bool > rb_tree<Key , Value , KeyOfValue , Compare , Alloc>::insert_unique(const Value &v) { rb_tree_node* y = header; rb_tree_node* x = root(); bool comp = true ; while (x != 0) { y = x; comp = key_compare(KeyOfValue()(v) , key(x)); x = comp ? left(x) : right(x); } iterator j = iterator(y); if (comp) { if (j == begin()) return pair<iterator , bool >(_insert(x , y , z) , true ); else --j; } if (key_compare(key(j.node) , KeyOfValue()(v) )) return pair<iterator , bool >(_insert(x , y , z) , true ); return pair<iterator , bool >(j , false ); } template < class Key , class Value , class KeyOfValue , class Compare , class Alloc> typename <Key , Value , KeyOfValue , Compare , Alloc>::_insert(base_ptr x_ , base_ptr y_ , const Value &v) { link_type x = (link_type) x_; link_type y = (link_type) y_; link_type z; if (y == header || x != 0 || key_compare(KeyOfValue()(v) , key(y) )) { z = create_node(v); left(y) = z; if (y == header) { root() = z; rightmost() = z; } else if (y == leftmost()) leftmost() = z; } else { z = create_node(v); right(y) = z; if (y == rightmost()) rightmost() = z; } parent(z) = y; left(z) = 0; right(z) = 0; _rb_tree_rebalance(z , header->parent); ++node_count; return iterator(z); } inline void _rb_tree_rebalance(_rb_tree_node_base* x , _rb_tree_node_base*& root) { x->color = _rb_tree_red; while (x != root && x->parent->color == _rb_tree_red) { if (x->parent == x->parent->parent->left) { _rb_tree_node_base* y = x->parent->parent->right; if (y && y->color == _rb_tree_red) { x->parent->color = _rb_tree_black; y->color = _rb_tree_black; x->parent->parent->color = _rb_tree_red; x = x->parent->parent; } else { if (x == x->parent->right) { x = x->parent; _rb_tree_rotate_left(x , root); } x->parent->color = _rb_tree_black; x->parent->parent->color = _rb_tree_red; _rb_tree_rotate_right(x->parent->parent , root); } } else { _rb_tree_node_base* y = x->parent->parent->left; if (y && y->color == _rb_tree_red) { x->parent->color = _rb_tree_black; y->color = _rb_tree_black; x->parent->parent->color = _rb_tree_red; x = x->parent->parent; } else { if (x == x->parent->left) { x = x->parent; _rb_tree_rotate_right(x , root); } x->parent->color = _rb_tree_black; x->parent->parent->color = _rb_tree_red; _rb_tree_rotate_left(x->parent->parent , root); } } } root->color = _rb_tree_black; } inline void _rb_tree_rotate_left(_rb_tree_node_base* x , _rb_tree_node_base*& root) { _rb_tree_node_base* y = x->right; x->right = y->left; if (y->left != 0) y->left->parent = x; y->parent = x->parent; if (x == root) root = y; else if (x == x->parent->left) x->parent->left = y; else x->parent->right = y; y->left = x; x->parent = y; } inline void _rb_tree_rotate_right(_rb_tree_node_base* x , _rb_tree_node_base*& root) { _rb_tree_node_base* y = x->left; x->left = y->right; if (y->right != 0) y->right->parent = x; y->parent = x->parent; if (x == root) root = y; else if (x == x->parent->right) x->parent->right = y; else x->parent->left = y; y->right = x; x->parent = y; }
linux 源码里的红黑树插入
自认为好读多了 O(∩_∩)O~
void rb_insert_color(rb_node *node, rb_root *root) { rb_node *parent, *gp; while ((parent = rb_parent(node)) && rb_is_red(parent)) { gp = rb_parent(parent); if (parent == gp->left) { register rb_node *uncle = gp->right; if (uncle && rb_is_red(uncle)) { <span style= "color:#ff0000;" > rb_set_black(parent); rb_set_black(uncle); rb_set_red(gp); node = gp; <span style="color:#ff0000;" > continue ; } if (node == parent->right) { _rb_rotate_left(parent, root); <span style="color:#ff0000;" > register rb_node *tmp = node; node = parent; parent = tmp; } rb_set_black(parent); <span style="color:#ff0000;" > rb_set_red(gp); _rb_rotate_right(gp, root); return ; } else { register rb_node *uncle = gp->left; if (uncle && rb_is_red(uncle)) { rb_set_black(parent); rb_set_black(uncle); rb_set_red(gp); node = gp; continue ; } if (node == parent->left) { _rb_rotate_right(parent, root); register rb_node *tmp = node; node = parent; parent = tmp; } rb_set_black(parent); rb_set_red(gp); _rb_rotate_left(gp, root); return ; } } rb_set_black(root->node); }