c++红黑树(小白慎入)

本文介绍了C++实现红黑树的详细过程,包括查找、插入和删除操作。通过实例解析了红黑树的性质和平衡调整策略,如左旋、右旋和颜色调整。并提供了相关操作的代码实现,帮助读者理解红黑树的运作机制。
摘要由CSDN通过智能技术生成

这一次我改用c++了,因为我嫌弃java没有指针。。。
写在前面,网上有红黑树插入删除可视化模拟器,可以插入删除规定范围内的数,可以感受感受,尤其是删除

  1. https://sandbox.runjs.cn/show/2nngvn8w
    对于这个网址,亲测无效,不知道你读这篇文章的时候有没有效。
    在这里插入图片描述

  2. 链接:https://pan.baidu.com/s/1_c7juIdNxjBPBSqHDPT7uA
    提取码:4jcn
    这个是我自己上传的,也是从网上找到的,如果有差池,那可能是我上传的出错了。亲测有效。


哈哈,红黑树,听起来好难得样子,其实呢?其实也很难!
啊?!?!
废话不多说,入主题。
您需要准备以下知识点:
二叉搜索树
指二叉树满足:
左子树小于根
右子树大于根
左右子树都是二叉搜索树
示例:
bst
更严格的定义,二叉搜索树的中序遍历是升序的。(其实大家都知道这个,没几个人提到?)
二叉平衡树
满足:
左子树和右子树高度差的绝对值不大于1
左右子树都是二叉平衡树
以下这一棵树严重畸形(不平衡):
畸形树
对于二叉搜索树,我原来就有一点不太明白。比如当b>a时,b是当a的右子节点还是a当b的左子节点?对效率有影响吗?(这里先埋一个伏笔,很深)
有时候。如果正好形成了一颗畸形树,将会非常非常慢,退化成链,当搜索的时候,一点一点爬,比蜗牛还慢。。。。
如果能保持平衡,那基本就是O(logn)的复杂度。
怎么保持平衡呢?红黑树登场了。
红黑树应满足:

  1. 每一个节点非黑即红。
  2. 黑土种的树,树根是黑的。
  3. 每一个节点的空子位置都挂着虚拟节点NULL,虚拟节点也是黑的。说明一下,下面画的图大部分没有画出虚拟节点,请打开脑洞,想象那里有一个虚拟节点。
  4. 红节点的每一个孩子必须是黑的(虚拟节点也算孩子呵呵呵)。推论:如果这个节点是红的,它的父亲必定不是红的(否则这个节点就是黑的),说明它的父亲必定为黑色,即父子不能都是红的,不代表黑色不行。
  5. 对于任意一个节点,它到任意一个后代叶子节点的路线中黑色节点相同。
  6. 红黑树是二叉搜索树,接近二叉平衡树。
  7. 有黑子,必有两个子。

好复杂鸭鸭!!怎么那么难??嘿嘿(●ˇ∀ˇ●),还没到最难的地方呢。。。
啥?!?!
举个栗子吧
我举个例子
栗子

到这里了,大家应该了解红黑树的性质了吧,现在我们就要鼓捣鼓捣各种操作了。。。
哦!!对了,还有“伏笔”呢,这里说明一下吧:
其实在二叉搜索树里,a的右子节点是b,而变成b的左子节点是a的这一步,叫做对a节点左旋
那这下子b原来的左子节点c交给谁管呢?那就交给a管吧,当它的右子节点。
其实可以想象为c先连接a,然后断开和b的连接,提起来b,就完成左旋了。这时候你会发现左旋并不破坏原有的搜索树性质,只会更改平衡性质(可能是变好也可能是变坏)。
上图吧:
左旋
右旋就是正好反过来,对于左旋,一切“左”变成“右”,一切“右”变成“左”,(连名字都遵循这规矩),就是右旋了。
以下段落为替换出来的结果:

其实在二叉搜索树里,a的左子节点是b,而变成b的右子节点是a的这一步,叫做对a节点右旋
那这下子b原来的右子节点c交给谁管呢?那就交给a管吧,当它的左子节点。
其实可以想象为c先连接a,然后断开和b的连接,提起来b,就完成右旋了。这时候你会发现右旋并不破坏原有的搜索树性质,只会更改平衡性质(可能是变好也可能是变坏)。

哈哈,其实我也挺懒的,直接替换就行了(顺便提一下,这里替换是有技巧的,三变量交换。左替换成哈喽,右替换成左,哈喽替换成右哈哈哈)
又跑题了。右旋也上个图吧:
右旋
还有一种叫变色,就是把颜色变一下(变成你想要的颜色),很容易理解吧。。。


现在鼓捣操作:

查找

查找其实和二叉搜索树一样:

注意!这些函数都是在类Node里面的!

T find(K ky){
   
   if(key==ky)return data;//命中目标 
   else if(ky<key && left != NULL)return left->find(ky);
   else if(ky>key && right != NULL)return right->find(ky);
   else return NULL;//失败 
  } 

因为红黑树是平衡的,所以复杂度也就差不多lgn,不会太慢,这背后的一切……
都是插入删除在不停的维护平衡啊!!!

插入

插入首先要找到要插入的位置。
和查找差不多。

void InsFix(Node* n){
   
   
  }
  bool insert(Node* node){
   
   if(key==node->key){
   //更新 
    data=node->data;
    return true;
   }
   bool res=false;
   if(node->key < key){
   //实际比当前小,往左走 
    if(left != NULL)res=left->insert(node);
    else{
   
     left=node;node->fa=this;
    }
   }else{
   //实际比当前大,往右走 
    if(right != NULL)res=right->insert(node);
    else{
   
     right=node;node->fa=this;
    }
   }
   InsFix(node); 
   return res;
  }

要插入什么颜色呢?当然是红色,因为红色不会太破坏平衡,能不破坏就不破坏。相反地,黑色特别容易破坏平衡,怪不得是黑色呢(联想一下黑客,就知道为什么是黑色了)。
InsFix是什么呢?就是要修补不平衡的情况:
其实这些情况是在插入函数内完成的:

1.树是空树:

设为根节点,并把颜色设为黑色
其实呢,这一种情况没有显式地表现出来。
调用insert的时候,必须有一个节点(有一个节点就不是空树了!!),使用它来调用insert,在子树插入。
此时,其实树不是空树。
那什么时候会出现这一种情况呢?
在这时:
Node* node;
默认就是黑色的(你可以指定它是黑色的,到文末代码部分时就会明白),所以它还是隐式地体现的。

2.节点已存在:

刷新数据

3.父节点为黑色(默认):

插入。


接下来要在InsFix实现:

4.父节点是红节点:

现在已经明显不符合性质4(红之子必黑)了。就需要修补了:

4.1.叔叔有并为红色:

4.1这是其中一种情况,还有的其实都一样,叔叔和爸爸变黑,爷爷变红。

这下得麻烦祖辈搞一搞平衡的事情了。。。
上面有个问题,如果爷爷是根肿么办?
那就把他的颜色硬拉回黑的呗,也不会破坏平衡,为什么?
假设会,说明是对于太爷爷,左子方向多了一个黑,右子没多黑,就不平衡了。
嗯???不是说好的爷爷就是首代了吗,还有爹?这个爷爷已经是女娲捏出来的了,真的不存在所谓的“太爷爷”了!
所以矛盾(在反证的时候最喜欢说矛盾二字了哈哈哈)
哈哈,不用管喽!!!😜○( ^皿^)っHiahiahia…

4.2.叔叔无或为黑:

为了精简,以下4.2.n为:
说明
上面一个标号的节点表示当前插入后的节点,它的标号n代表这个位置会在4.2.n说到

4.2.0

4.2.0
爹变黑,爷爷变红,对爷爷右旋。
其实自己(刚插入的节点)变黑在右旋也行,只不过不想在找祖辈麻烦了~~
如果麻烦了,等着T飞就对了<( ̄︶ ̄)↗[GO!]

4.2.1

4.2.1
其实后面的部分是Ctrl+CV过来的,添加了点东西而已ο(=•ω<=)ρ⌒☆
对b左旋,InsFix(b)就OK了。

4.2.3

啊啊啊啊????怎么0,1,蹦到3了??应该是2吧。难道是打错了??
NO!因为4.2.2是基于4.2.3的,所以先来这个(✿◕‿◕✿)
(⊙_⊙)?
(⊙﹏⊙)
没见过数数这么数的鸭。。。
0,1,3???!!!!!!
Pia!(o ‵-′)ノ”(ノ﹏<。)
4.2.3
爸爸变黑,爷爷变红,对爷爷左旋。

4.2.2

呵呵,这回回到2了。
4.2.2
对b右旋,InsFix(b),OK。
插入情景完~~
.<{=....
灵魂拷问,你真的理解了吗?
来一道题吧。。。加深印象

do{
   
	读一遍所有插入情景;
}while(!会做题);

习题
答案
最后贴上代码:

void leftturn() {
   
		Node* myfa = fa;
		Node* rl = right->left;
		fa = right;
		right->left = this;
		right->fa = myfa;
		if (myfa != NULL) {
   
			if (myfa->left == this)myfa->left = right;
			else myfa->right = right;
		}
		right = rl;
		if (rl != NULL)right->fa = this;
	}
	void rightturn() {
   
		Node* myfa = fa;
		Node* lr = left->right;
		fa = left;
		left->right = this;
		left->fa = myfa;
		if (myfa != NULL) {
   
			if (myfa->left == this)myfa->left = left;
			else myfa->right = left;
		}
		left = lr;
		if (lr != NULL)left->fa = this;
	}
	void InsFix(Node* n) {
   
		if (n->fa->color == true) {
   
			Node* l = n->fa->fa->left;
			Node* r = n->fa->fa->right;
			Node* uncle = (l == n->fa ? r : l);
			if (uncle == NULL || uncle->color == false) {
   //叔叔黑 
				int c = ((n->fa->fa->right == n->fa) << 1) + (n->fa->right == n);//判断情况
				switch (c) {
   
				case 0:
					n->fa->color = false;//爸爸变黑
					n->fa->fa->color = true;//爷爷变红
					n->fa->fa->rightturn();//爷爷右旋
					break;
				case 1:
					n->fa->leftturn();//爸爸左旋
					InsFix(n->left);//再做case0处理
					break;
				case 2:
					n->fa->rightturn();//爸爸右旋
					InsFix(n->right);//再做case3处理
					break;
				case 3:
					n->fa->color = false;//爸爸变黑
					n->fa->fa->color = true;//爷爷变红
					n->fa->fa->leftturn();//爷爷左旋
					break;
				}
			}
			else if (uncle->color == true) {
   //叔叔是红的
				n->fa->fa->left->color = false;
				n->fa->fa->right->color = false;//把爷爷的两个孩子(必有一个爸爸和一个叔叔)都设成黑色的
				if (n->fa->fa->fa != NULL)
				{
   

					n->fa->fa->color = true;
					InsFix(n->fa->fa);
				}

			}
		}
	}
	bool insert(Node* node) {
   
		if (key == node->key) {
   //更新 
			data = node->data;
			return true;
		}
		bool res = false;
		if (node->key < key) {
   //实际比当前小,往左走 
			if (left != NULL)res = left->insert(node);
			else {
   
				node->color = true;
				left = node; node->fa = this;
				InsFix(node);
			}
		}
		else {
   
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值