红 - 黑树是一种平衡二叉树被广泛用于,但对于很多人谁是新算法,红黑树实在是太复杂,看之前July博文,觉得自己的写作非常具体的。不过还是有点乱。不能急。要每天看一点点,所以我把插入和删除分开来写,仅仅要看懂并记住插入后是如何操作的,那么删除也就easy了。
红黑树的规则:
性质1. 节点是红色或黑色。
性质2. 根是黑色。
性质3. 全部叶子都是黑色(叶子是NIL节点)。
性质4. 每一个红色节点的两个子节点都是黑色。(从每一个叶子到根的全部路径上不能有两个连续的红色节点)
性质5. 从任一节点到其每一个叶子的全部简单路径都包括同样数目的黑色节点。
最基本的两条是4和5。一旦插入一个新的节点,那么4和5的性质就有可能破坏。从5中我们能够推出新增节点必须设为红色,而再依据4。新增节点的父节点必须是黑色。
我们推出的这最后一条很重要。由于一旦发现插入节点要插在一个红色节点下,就要開始折腾了!
红黑树的最基本操作是旋转(左旋和右旋)。因为对称性,将一个即可。
下图介绍右旋的一个样例(本文图皆来自《STL源代码剖析》)
我们从图中发现,右旋事实上就是改变了k1,k2的父子关系,那么其它节点怎么变呢?
能够这样理解。父亲要和儿子换位置,父亲好悲剧,那父亲的还有一个儿子和他的父子关系不能变(再变就太慘了),儿子长辈分了,那么儿子就把他的儿子交给原来的父亲当儿子吧,而操作是右旋,那就移动儿子的右孩子。
简记:右旋:孩子的右孩子给父亲当左孩子。
左旋:孩子的左孩子给父亲当右孩子。
默念三遍。就会发现旋转的思路就是这么简单!
代码例如以下(STL源代码。大师作品):
inline void _rb_rotate_right(_rb_tree_node_base* x,_rb_tree_node_base*& root)\\x为k2。root为根节点 _rb_tree_node_base* y=x->left; \\y为左孩子。也就是k1 x->left=y->right; \\孩子的左孩子成为父亲的右孩子 B接k2 if(y->right!=0) y->right->parent=x; \\ 回马枪设定父节点 y->parent=x->parent; \\k1開始跑到k2的位置 if(x==root) root=y; else if(x==x->parent->right) x->parent->right=y; \\y代替x的地位,但要和x的原父亲的关系还原 else x->parent->left=y; y->right=x; x->parent=y; \\最后,父亲变成孩子,悲剧。 }
代码思路:1先把孩子的左孩子接到这个节点的右孩子上(2句) 2这个右孩子的父亲是这个节点(1句) 3、节点的左孩子代替节点的位置(1句) 4、和原节点父亲节点的关系保留(3种推断) 5、节点成为孩子节点的孩子(2句)
以下进入正题:插入节点
插入节点可能会带来3种不同情况的破坏,下图给出的是4种情况:(4仅仅是3的更复杂一点的情况)
我们细致观察这三种不同情况,同样的是插入节点的父亲都是红的。不同点有2:1是父亲的兄弟节点(叔节点)是什么颜色的?2新增节点是作为左孩子还是右孩子?
第一种情况:
叔黑+左孩子(黑左bl)
解决方法:右旋。
例如以下图:
说是右旋,旋哪个呢?新增节点肯定不动,以新节点的父亲P,爷爷为轴右旋G。旋转后这两个节点颜色都变(新增点肯定是红的)。
简记:黑左(bl)=右旋PG
助记:bl=r+pg(Boys’ Love RPG)
另外一种情况:
叔黑+右孩子(br)
解决方法:左旋+右旋
例如以下图:
我们从图中能够看出,先左旋X和P,再右旋X和G(变化X和G)。细致想想,第一次左旋似乎就是构造出第一种情况(图2先把8和10的颜色变化了,应是8红,10黑,那就和第一种情况一样)。
简记:黑右(br)=左旋XP。到达叔左。
助记:嘿哟,郎咸平(lXP)
第三种情况
叔红
这样的情况很easy。我们肯定叔节点不能有孩子了,有的话必须是黑色的,那叔节点那条分支的黑色就多了,所以我们能够随便改变叔节点的颜色(这是前提)。
例如以下图(来自July)
4是新增节点,4和5都红了。那就把5变黑,5黑后为了平衡的把8(叔节点)也变黑,都黑了后7这条分支比1分支多了一个黑色节点。所以7也要红,这样和2又都是红色的了,还得往上递归。这时候7相当玉新插入节点。得三种情况推断(图中这个符合另外一种情况),一直递归到根节点。
简记:变变变(PGU都变了),再推断。
最后来看SLT里面大师是如何写源码的。
首先,他的思路有所变换,他感觉第三种情况一直往上递归太慢,所以他採用了先处理的方法,例如以下图:
大致思路是:沿着插入节点往上看。假设有一个节点的两个子节点都是红色。那就把这个节点改成红色,他的子节点都改成黑色。假设此时这个节点的父节点也是红色(此时父节点的兄弟节点一定是黑的,请自己分析)就要像第一种或者第一种情况处理。
最后,插入节点的推断就仅仅剩第1和第2两种情况。
代码例如以下:
// 这是一个全局函数 // 又一次令树形平衡(改变颜色及旋转树形) inline void _rb_tree_rebalance(_rb_tree_node_base* x , _rb_tree_node_base*& root) //x新增节点,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; // 令y为伯父节点 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; // 令y为伯父节点 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); // 第一个參数为左旋点 } } }//while root->color = _rb_tree_black; // 根节点永远为黑色 }
事实上,红黑树确实挺复杂的,变化太多,但仅仅要略微注意下几种情况的相互关系,比方3->2->1,理解就简单多了。
下一步学习删除节点。
版权声明:本文博客原创文章,博客,未经同意,不得转载。