红黑树原理详解

参考:
教你初步了解红黑树
红黑树(一)之 原理和算法详细介绍

红黑树定义:
(1) 每个节点或者是黑色,或者是红色。
(2) 根节点是黑色。
(3) 每个叶子节点是黑色。 [注意:这里叶子节点,是指为空的叶子节点!]
(4) 如果一个节点是红色的,则它的子节点必须是黑色的。
(5) 从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。

如下图,是一颗简单的红黑树。其中Nil为叶子结点,并且它是黑色的
在这里插入图片描述

黑高度
从某个节点x出发(不包含该该节点)到达一个叶子节点的任意一条路径上,黑色节点的个数称为该节点x的黑高度,用bh(x)表示。从该结点出发的所有下降路径都具有相同的黑节点个数。
红黑树黑高度的定义为其根节点的黑高度。

性质
一棵含有n个内节点的红黑树的高度至多为2log(n+1).
即,包含n内结点的红黑树是高度为O(lgn)的查找树,简单操作的运行时间为O(lgn)。

证明
首先定义一个节点x的黑高为 b h ( x ) bh(x) bh(x),表示从x到任意一个叶子节点路径上黑色节点的个数(不包括x)。

1.第一步,先证明以某一节点x为根的子树中至少包含 2 b h ( x ) − 1 2^{bh(x)}−1 2bh(x)1个内节点(不是叶子的都是内节点)。用数学归纳法证明。
如果x的高度为0,那么x是叶节点,包含0个内节点,满足该式子。
对于高度为正值的x,其两个孩子至少包含 2 b h ( x ) − 1 − 1 2^{bh(x)−1}−1 2bh(x)11个内节点,
所以以x为根的子树至少包含 ( 2 b h ( x ) − 1 − 1 ) + ( 2 b h ( x ) − 1 − 1 ) + 1 = 2 b h ( x ) − 1 (2^{bh(x)−1}−1)+(2^{bh(x)−1}−1)+1=2^{bh(x)}−1 (2bh(x)11)+(2bh(x)11)+1=2bh(x)1个内节点。

2.第二步,对于一棵高度为h的树,任意一条从根到叶节点(不包括根)的路径上至少有一半黑色节点,从而 b h ( x ) ≥ h / 2 bh(x)≥h/2 bh(x)h/2,所以 n ≥ 2 b h ( x ) − 1 ≥ 2 h / 2 − 1 n≥2^{bh(x)}−1≥2^{h/2}−1 n2bh(x)12h/21,即 h ≤ 2 l o g ( n + 1 ) h≤2log(n+1) h2log(n+1)

红黑树的旋转

直接看这里
https://www.cnblogs.com/skywang12345/p/3245399.html

红黑树上结点的插入

STL源码剖析—红黑树原理详解上

1、黑父
如下图所示,如果新节点的父结点为黑色结点,那么插入一个红点将不会影响红黑树的平衡,此时插入操作完成。红黑树比AVL树优秀的地方之一在于黑父的情况比较常见,从而使红黑树需要旋转的几率相对AVL树来说会少一些。
在这里插入图片描述

2、红父
如果新节点的父结点为红色,这时就需要进行一系列操作以保证整棵树红黑性质。如下图所示,由于父结点为红色,此时可以判定,祖父结点必定为黑色。这时需要根据叔父结点的颜色来决定做什么样的操作。青色结点表示颜色未知。由于有可能需要根结点到新点的路径上进行多次旋转操作,而每次进行不平衡判断的起始点(我们可将其视为新点)都不一样。所以我们在此使用一个蓝色箭头指向这个起始点,并称之为判定点。
在这里插入图片描述

2.1 红叔
当叔父结点为红色时,如下图所示,无需进行旋转操作,只要将父和叔结点变为黑色,将祖父结点变为红色即可。但由于祖父结点的父结点有可能为红色,从而违反红黑树性质。此时必须将祖父结点作为新的判定点继续向上(迭代)进行平衡操作

这种称为LLr型,(表示祖父的左孩子L的左孩子L,同时叔叔是红色r),下面名称可以类比。
在这里插入图片描述
需要注意的是,无论“父节点”在“叔节点”的左边还是右边,无论“新节点”是“父节点”的左孩子还是右孩子,它们的操作都是完全一样的(其实这种情况包括4种,只需调整颜色,不需要旋转树形)。

其实还有LRr型(在父节点处左旋则可得到LLr型),不要讨论了。
RLr型(与LRr型对称),RRr型(与LLr对称),这两种对称情况也不用讨论了。

2.2 黑叔
当叔父结点为黑色时,需要进行旋转,以下图示了所有的旋转可能:
Case 1: LLb型
在这里插入图片描述

Case 2: LRb型
在这里插入图片描述

Case 3: RLb型(与LRb型对称)
在这里插入图片描述

Case 4: RRb型与LLb型对称)
在这里插入图片描述

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

红黑树上结点的删除

STL源码剖析—红黑树原理详解下

第一步:将红黑树当作一颗二叉查找树,将节点删除。

我们先回忆一下前驱节点和后继节点的定义

前驱节点:二叉树中序遍历完成后和这个节点相邻的前面的节点为该节点的前驱节点
后继节点:二叉树中序遍历完成后和这个节点相邻的后面的节点为该节点的后继节点

再看看普通二叉树的节点删除方法:

Z指向需要删除的节点,Y指向实质结构上被删除的结点,

① 被删除节点没有儿子,即为叶节点。那么,直接将该节点删除就OK了。即Y指向Z。

② 被删除节点只有一个儿子。那么,直接删除该节点,并用该节点的唯一子节点顶替它的位置。即Y指向Z。

③ 被删除节点有两个儿子。那么,先找出它的后继节点;然后把“它的后继节点的内容”复制给“该节点的内容”;之后,删除“它的后继节点”。即Y指向Z的后继节点。

在这里,后继节点相当于替身,在将后继节点的内容复制给"被删除节点"之后,再将后继节点删除。这样就巧妙的将问题转换为"删除后继节点"的情况了,下面就考虑后继节点。 在"被删除节点"有两个非空子节点的情况下,它的后继节点不可能是双子非空。既然"的后继节点"不可能双子都非空,就意味着"该节点的后继节点"要么没有儿子,要么只有一个儿子。若没有儿子,则按"情况① "进行处理;若只有一个儿子,则按"情况② "进行处理。

下面是一份在二叉搜索树中删除一个节点的代码示例

/***************************************
* 在BST中删除一个节点
* 先找到要删除的结点,假设为A,A要分三种情况
*  1.A两个子结点为NULL,则直接删除A
*  2.A只有一个非空子结点,
*  3.A有两个子非空结点,
*****************************************/
Node* getMaxOfLeftTree(Node* leftRoot) {
	if (leftRoot == NULL)
		return NULL;
	while (leftRoot->right)
		leftRoot = leftRoot->right;
	return leftRoot;
}
Node* getMinOfRightTree(Node* rightRoot) {
	while (rightRoot->left != NULL)
		rightRoot = rightRoot->left;
	return rightRoot;
}

Node* deleteNode(Node* root, int key) {
	if (root == NULL)
		return NULL;//

	if (root->data == key)//左子树的最大结点或右子树的最小结点
	{
		//情况1
		if (root->left == NULL && root->right == NULL) {
			delete root;
			return NULL;
		}			
		//排除情况1后,情况2
		//只有右结点
		if (root->left == NULL) {
			Node* temp = root->right;
			delete root;
			return temp;
		}	
		//只有左结点
		if(root->right==NULL) {
			Node* temp = root->left;
			delete root;
			return temp;
		}
		//情况3
		Node* minNode = getMinOfRightTree(root->right);
		root->data = minNode->data;
		//很重要,这里是递归删除,为什么不能直接删除这个右子树的最小/左结点
		//因为这个右子树的最小/左节点可能有一个右结点,所以不能直接删除
		root->right = deleteNode(root->right, minNode->data);
	}
	else if (root->data < key)
		root->right=deleteNode(root->right, key);
	else if (root->data > key)
		root->left=deleteNode(root->left, key);

	return root;
}

第二步:通过"旋转和重新着色"等一系列来修正该树,使之重新成为一棵红黑树。

因为"第一步"中删除节点之后,可能会违背红黑树的特性。所以需要通过"旋转和重新着色"来修正该树,使之重新成为一棵红黑树。

再看一下红黑树的几个特性:

(1) 每个节点或者是黑色,或者是红色。
(2) 根节点是黑色。
(3) 每个叶子节点是黑色。 [注意:这里叶子节点,是指为空的叶子节点!]
(4) 如果一个节点是红色的,则它的子节点必须是黑色的。
(5) 从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。

1.如果Y指向的节点是个红色节点,那么直接删除掉Y以后,红黑性质不会被破坏。操作结束。

2.如果Y指向的节点是个黑色节点,那么就有几条红黑性质可能受到破坏了。
首先是包含Y节点的所有路径,黑高度都减少了一(第5条被破坏)。

其次,如果Y有红色子节点,Y又有红色的父节点,那么Y被删除后,就出现了两个相邻的红色节点(第4条被破坏)。

最后,如果Y指向的是根节点,而Y的子节点又是红色的,那么Y被删除后,根节点就变成红色的了(第2条被破坏)。

其中,第5条被破坏是让我们比较难受的。因为这影响到了全局。这样动作就太大太复杂了。而且在这个条件下,进行其它红黑性质的恢复也很困难。

所以我们首先解决这个问题:如果不改变含Y路径的黑高度,那么树的其它部分的黑高度就必须做出相应的变化来适应它。所以,我们想办法恢复原来含Y节点的路径的黑高度。做法就是:无条件的把Y节点的黑色,推到它的子节点X上去。(X可能是NIL节点)。这样,X就可能具有双重黑色,或同时具有红黑两色,也就是第1条性质被破坏了

但第1条性质是比较容易恢复的:
一、如果X是同时具有红黑两色,那么好办,直接把X涂成黑色,就行了。而且这样把所有问题都解决了。因为将X变为黑色,2、4两条如果有问题的话也会得到恢复,算法结束。

二、如果X是双黑色,那么我们希望把这种情况向上推一直推到根节点(调整树结构和颜色,X的指向新的双黑色节点,X不断向上移动),让根节点具双黑色,这时,直接把X的一层黑色去掉就行了(因为根节点被包含在所有的路径上,所以这样做所有路径同时黑高减少一,不会破坏红黑特征)。
具体可以概括为3种情况。
① 情况说明:x是“红+黑”节点。
处理方法:直接把x设为黑色,结束。此时红黑树性质全部恢复。

② 情况说明:x是“黑+黑”节点,且x是根。
处理方法:什么都不做,结束。此时红黑树性质全部恢复。

③ 情况说明:x是“黑+黑”节点,且x不是根。

在③的情况下,X节点是双层黑色,且X有父节点P(X不是根,所以必定有父亲)。同时,X必然有兄弟节点W,而且这个W节点必定有两个子节点。(因为这是原树满足红黑条件要求而自然具备的。X为双黑色,那么P的另一个子节点以下一定要有至少两层的节点,否则黑色高度不可能和X路径一致)
所以我们就分析这些节点之间如何变形,把问题限制在比较小的范围内解决。

另一个前提是:X在一开始,肯定是树底的叶节点或是NIL节点,所以在递归向上的过程中,每一步都保证下一步进行时,至少 X的子树是满足红黑特性的。
因此子树的情况就可以认为是已经正确的了,

这样,分析就只限制在X节点,X的父节点P和X的兄弟节点W,以及W的两个子节点。这些个节点中

在X具有双重黑色的情况下,算法的过程就是将这多出的一重黑色向上移动,直到遇到红节点或者根节点。

接着往下分析, 会遇到4种情况,实际上是8种, 因为其中4种是相互对称的,这可以通过判断X是其父节点的右孩子还是左孩子来区分。

下面我们以X是其父节点的左孩子的情况来分析这4种情况,实际上接下来的调整过程,就是要想方设法将经过X的所有路径上的黑色节点个数增加1

具体分为以下四种情况:(下面针对x是左儿子的情况讨论,右儿子对称)

Case1:X的兄弟W是红色(想办法将其变为黑色)
由于W是红色的,因此其儿子节点和父节点必为黑色,只要将W和其父节点的颜色对换,在对父节点进行一次左旋转,便将W的左子节点放到了X的兄弟节点上,X的兄弟节点变成了黑色,且红黑性质不变。但还不算完,只是暂时将情况1转变成了下面的情况2或3或4。

在这里插入图片描述

Case2:X的兄弟节点W是黑色的,而且W的两个子节点都是黑色的,此时可以将X的一重黑色和W的黑色同时去掉,而转加给他们的父节点上,这时X就指向它的父节点了,因此此时父节点具有双重颜色了。这一重黑色节点上移。
在这里插入图片描述
如果父节点原来是红色的,现在又加一层黑色,那么X现在指向的这个节点就是红+黑两色的,直接把X(也就是父节点)着为黑色。问题就已经完整解决了。

如果父节点现在是双层黑色,那就以父节点为新的X进行向上的下一次的递归。

Case3:X的兄弟节点W是黑色的,而且W的左子节点是红色的,右子节点是黑色的
此时通过交换W和其左子节点的颜色并进行一次向右旋转就可转换成下面的第四种情况。注意,原来L是红色的,所以L的子节点一定是黑色的,所以旋转中L节点的一个子树挂到之后着色为红色的W节点上不会破坏红黑性质。变形后黑色高度不变。

在这里插入图片描述
Case4:X的兄弟节点W是黑色的,而且W的右子节点是红色的。
这种情况下,做一次左旋,W就处于X父亲A的位置,将W保持为原来的A的位置的颜色,同时将W的两个新的儿子节点的颜色变为黑色,去掉X的一重黑色。这样整个问题也就得到了解决。递归结束。(在代码上,为了标识递归结束,我们把X指向根节点)

在这里插入图片描述
因此,只要按上面四种情况一直递归处理下去,X最终总会指向根结点或一个红色结点,这时我们就可以结束递归并把问题解决了。

4种情况总结如下表

现象说明处理策略
Case 1X是"黑+黑"节点,X的兄弟节点W是红色。(01) 将X的兄弟节点W设为“黑色”。
(此时X的父节点和X的兄弟节点W的子节点都是黑节点)(02) 将X的父节点设为“红色”。
(03) 对X的父节点进行左旋
(04) 左旋后,重新设置X的兄弟节点W
Case 2x是“黑+黑”节点,x的兄弟节点是黑色,x的兄弟节点的两个孩子都是黑色(01) 将X的一重黑色和其兄弟节点W的黑色同时去掉,而转加给他们的父节点上,此时父节点具有双重颜色了
(02) 设置“X的父节点”为“新的X节点”。
Case 3X是“黑+黑”节点,X的兄弟节点W是黑色;X的兄弟节点W的左孩子是红色,右孩子是黑色的。(01) 将X兄弟节点W的左孩子设为“黑色”。
(02) 将X兄弟节点W设为“红色”。
(03) 对X的兄弟节点W进行右旋。
(04) 右旋后,重新设置X的兄弟节点W。
Case 4X是“黑+黑”节点,x的兄弟节点W是黑色;X的兄弟节点W的右孩子是红色的,X的兄弟节点W的左孩子任意颜色。(01) 将X父节点颜色赋值给X的兄弟节点W(即,将W保持为原来X的父亲的颜色)。
(02) 将X父节点设为“黑色”。
(03) 将X兄弟节点W的右子节设为“黑色”。
(04) 对X的父节点进行左旋。
(05) 设置“X”为“根节点”(这里是为了标识递归结束)。

代码就不贴了,具体的可以看看上面的几篇参考博文。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值