一、红黑树的介绍
红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是红(red)或黑(black)。通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出两倍,因此是接近平衡的。(近似平衡)AVL树是严格平衡
红黑树有以下性质(规则):
1.每个结点不是红色就是黑色
2.根结点是黑色的
3.如果一个结点是红色的,则它的两个孩子结点是黑色的。(一条路径中没有连续红色结点)
4.对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点(每条路径的黑色结点数量相等)
5.每个叶子结点都是黑色的(此处的叶子结点指的是空结点)
以上四点,就能保证红黑树确保没有一条路径会比其他路径长出两倍(最短*2>=最长)第五点我们放在后面解释。
由于其底层也是二叉搜索树,结点的封装我们就不多说了(存放的还是pair类型,结点中包含其左右结点和父结点的指针)除此之外,我们还要用枚举记录颜色,基本就是如图:
颜色给什么我们一会再说。
二、红黑树的基本功能实现
1.插入数据
此时会面临一个选择, 是插入红色还是黑色,这要看插入后对其他结点的影响大不大,对于红色,我们只要保证路径无连续红色即可,即使插入后有连续红色也只是在这一条路径上更改,但黑色就不一样了,需要保证每条路径的黑结点数量相等,一旦一条路插入黑色,所有的其他路径都要修改,代价和红色比还是很大的。所以,我们先讨论插入红色结点的情况
以下面这棵树(或子树)为例
其中,p代表parent g代表grandfather u 代表uncle
前提条件:abcde均为空,cur为新增
我们一开始先不考虑用旋转来解决问题,毕竟旋转的操作影响比较大,且目的是实现完全平衡,但我们的红黑树的目的是实现近似平衡即可,因此尽量减少工作量。
其中,以上有几个结点的颜色已经是确定的:cur红色结点,parent一定也是红色结点(因为如果parent是黑色结点那么我们cur的插入操作就符合红黑树的规则,不需要再调整了,我们这里所有情况都是当cur插入后树不符合红黑树的规则下),grandfather一定是黑色(parent已经是红了,一条路不可能出现连续红色)。因此,唯一可变性就在这个uncle结点。
情况一:当uncle存在且是红结点,为了保证规则,首先需要把parent转变为黑,同时把uncle变红,而且要把grandfather变红(因为每个路径的黑结点数量一定是相同的,经过上面的操作后,两条路径均新增一个黑结点,为了保持和别的路径相等,需要把grandfather变红保持相等,但如果grandfather 本身是根就没必要)然后再向上调整,把grandfather当成cur调整。
在情况一的前提条件下,ab为红,cde为只有一个黑色结点的子树,那么其情况就有上百种,我们在后续也就不过多分析了。
情况二之一:当uncle不存在(abcde均为空,cur为新增且为parent的左) 如下图左边
此时如果我们像情况一的处理方式貌似无法解决问题,破坏了黑色数量的平衡,因此我们只能旋转处理(上图右为旋转后的结果)
情况二之二:当uncle不存在(abcde均为空,cur为新增且为parent的右)
此时我们通过单旋就无法满足要求了,需要进行双旋,先以parent为中心进行一次做单旋,此时情况就变成了情况二之一,再加个以g为中心的右旋即可满足。
情况三:uncle存在且为黑色
处理方法:旋转(以g为中心的右单旋),让g的位置变成p,p的左不变,右变为g,g右不变,左变成c,然后p变黑,g变红
其实,无论uncle是空还是为黑色,其操作原理完全相同,因此我们当只需要单旋成为情况二,双旋为情况三
我们用代码分析一下以上的所有情况,还是和前面的类似,需要写在插入函数中,而且,其中左旋和右旋的函数在上节的AVL树已经讲解完原理,所以代码中对这部分就不讲解了(这边附上AVL树的链接https://blog.csdn.net/czt230610/article/details/135952308?fromshare=blogdetail&sharetype=blogdetail&sharerId=135952308&sharerefer=PC&sharesource=czt230610&sharefrom=from_link)
//....上面是插入函数while(parent&&parent->_col==RED)//必须为红色,否则插入后无需修改{Node*grandfather =parent->_parent;if(parent==grandfather->_left) //大前提,研究的都是左边一边高{ //当右边高的时候代码原理相同Node*uncle=grandfather->right;if(uncle&&uncle->_col==RED) //情况一{parent->_col=BLACK;uncle->_col=BlACK;grandfather->_col=RED;cur=grandfather;parent=cur->_parent;}else //uncle不存在或者为黑色 情况二{if(cur==parent->_left) //一边高,单旋即可{rotater(grandfather);//改色parent->_col=BLACK;grandfather->_col=RED;}else//不是一边高,双旋 情况三{rotatel(parent);rotater(grandfather);//改色cur->_col=BLACK;grandfather->_col=RED;}break;}} else//parent在右uncle在左{Node* uncle = grandfather->_left;// 叔叔存在且为红,-》变色即可if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;// 继续往上处理cur = grandfather;parent = cur->_parent;}else // 叔叔不存在,或者存在且为黑{// 情况二:叔叔不存在或者存在且为黑// 旋转+变色// g// u p// cif (cur == parent->_right){RotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{// g// u p// cRotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;} }_root->_col = BLACK;//无论情况一之后是否需要向上调整都把根变黑//避免了多情况分析
}
正文结束
麻烦留下你宝贵的点赞与收藏,这对我的动力真的很大!