C++数据结构之红黑树

一.概念

红黑树是一棵二叉搜索树,每个节点增加一个存储位,来存储节点的颜色。节点颜色只有红色和黑色两种。通过对任何⼀条从根到叶⼦的路径上各个结点的颜⾊进⾏约束,最长路径不超过最短路径的二倍 ,也就是说,一棵红黑树的高度为h,最短路径为h/2,其他路径长度都在它俩之间。那么整棵树进而达到接近平衡的状态。所以它控制平衡没有AVL树那么严格。

1.红黑树的规则

2.为何最长路径不超过最短路径的二倍?

也就是说,规则第二,三点限定最长。规则第四点限定最短。

3.红黑树的开销

N位于每条路径全是黑(H = h),以及每条路径全是红黑交替(H = 2h)两个极端以内,而通过等比数列求和即可求出上面N的范围。

二.红黑树的实现

1.结构

2.插入

2-1.插入的过程

1.插入一个值,必须按照二叉搜索树的规则插入,插入后还要判断整棵树是否符合红黑树四规则。

2.对于非空树,插入黑色节点必然违反规则四,而插入红色节点有可能违反规则三,更何况规则四是很难维护的,所以新插入节点为红色节点。而对于空树,新插入节点必须为黑节点,否则违反了规则二。

3.非空树新插入的节点必为红色 ,如果该节点的父节点为黑色,则符合规则三,插入结束。

4.非空树新插入的节点必为红色,如果该节点的父节点为红色,违反规则三,需进一步分析,调整。

这几个亲属节点中,唯uncle节点颜色不定,其余几个均为固定颜色。所以要根据u节点,分情况处理(关键看叔叔):

2-2情况1.纯变色(u不为空,且为红)

p,u为红,g为黑,直接来个换家操作,g变红,p,u变黑,c不变,还是红。

首先得明晰,无论怎么讲,只要在非空树新插入的节点必为红色该节点的父节点为红色 这个大前提下,去做处理,p节点必然要被处理成黑色,因为不能有父子双红。p变黑以后,如果不做其他操作,那么该条支路的黑节点数就会增加1,就违背了规则四,于是就把g变成红,增加的黑节点数就又恢复了。既然g变红,u节点那一支也不能出现父子双红,所以u也变红。

总结:
抽象化
子树a,b,d,e,f的具象化

Ⅰ.a,b,c,d,e,f子树全为空,c为新插入节点

此时d,e,f子树bh = 0

Ⅱ.c不是新插入节点,为之前的g节点(黑色)

此时d,e,f子树bh = 1

新插入节点的插入位置在a,b的4个子节点里选一个。

Ⅲ.c不是新插入节点,为之前的g节点(黑色)

但d,e,f子树的bh = 2

无论子树d,e,f的bh被设计的有多大,子树变的多复杂,万变不离其宗,在处理时都向上,c移到当下子树g节点处,反复将上面子树的父亲,叔叔变黑,爷爷变红,直到整棵树满足红黑树规则。

代码实现
复制代码
//父亲也为红,出现父子连续双红,需要处理
while (Parent && Parent->_col == RED)
{
	Node* grandfather = Parent->_parent;
	//叔叔存在
	if (grandfather->_left == Parent)
	{
		 //g
		//p u 叔叔在右
		Node* uncle = grandfather->_right;
		if (uncle && uncle->_col == RED)
		{//叔叔存在且为红。变色
			Parent->_col = uncle->_col = BLACK;
			grandfather->_col = RED;
		 //向上调整
			cur = grandfather;
			cur->_parent = Parent;
		}
	}
	else
	{//叔叔在左
		Node* uncle = grandfather->_left;
	}
}
//parent为空,也就是cur走到根节点的位置,并且根节点为红色。
//直接给_root改色即可,反正把它改成黑色,每个支路上黑节点总数都加1,还是相同的。
//即便根本来就是黑色,再让它成黑色也没问题。
_root->_col = BLACK;
return true;

2-3情况2.单旋+变色(u为空或为黑)

①.g的_parent(18)为黑

所以需要先对gpc这棵子树进行单旋,然后g和p再变色:

②.g的_parent(18)为红

上面操作还未完,假如g->_parent为红,旋转+变色后g和g->_parent出现父子双红。还需要继续调整,把g当作c,向上更新。如果g->_parent还是红,就接着向上调;如果g->_parent是黑,就结束;如果g是整棵树根节点,还是红色,就把它变成黑。

总结:
子树抽象化:

u为空,或者u存在且为黑。

u为空:

此时c为新增节点

u存在且为黑:

c经向上调整后移到之前子树的g节点处,并且该节点变成红色。

核心就是g先单旋,再改变p,g的颜色

子树具象化:

2-4情况3.双旋+变色(u为空或为黑)

u为空:

双旋后,c做了当前子树根节点,且p,g变成了子节点,需要将c变黑,g变红,才能符合规则。

u存在且为黑:

由于旋转这几种情况,在旋转+变色以后,子树的根节点都会变成黑色,所以无需再向上调整。而叔叔存在且为红的情况,旋转+变色后会让子树根节点变成红色,此时上面一棵子树parent也为红,连续双红需要向上调整。

2.5.插入的完整代码实现及其思维导图

复制代码
bool insert(const pair<K, V>& kv)
{//遍历
	if (_root == nullptr)
	{
		_root = new Node(kv);
		_root->_col = BLACK;//空树插入节点是黑的。
		return true;
	}
	Node* Parent = nullptr;
	Node* cur = _root;
	while (cur)
	{
		if (cur->_kv.first < kv.first)
		{//cur和待插入节点的值进行比较,待插入值大于cur,cur往右子树走,小于则往左子树走。
			Parent = cur;
			cur = cur->_right;
		}
		else if (cur->_kv.first > kv.first)
		{
			Parent = cur;
			cur = cur->_left;
		}
		else
		{
			return false;
		}
	}
	//插入
	cur = new Node(kv);
	cur->_col = RED;//非空树插入节点是红的
	if (Parent->_kv.first < kv.first)
	{
		Parent->_right = cur;
	}
	if (Parent->_kv.first > kv.first)
	{
		Parent->_left = cur;
	}
	//链接父节点
	cur->_parent = Parent;
	//父亲也为红,出现父子连续双红,需要处理
	while (Parent && Parent->_col == RED)
	{
		Node* grandfather = Parent->_parent;
		if (grandfather->_left == Parent)
		{//叔叔在右
			 //g
			//p u 
			Node* uncle = grandfather->_right;
			if (uncle && uncle->_col == RED)
			{//叔叔存在且为红。变色
				Parent->_col = uncle->_col = BLACK;
				grandfather->_col = RED;
			 //向上调整
				cur = grandfather;
				cur->_parent = Parent;
			}
			else
			{//uncle存在且为黑或叔叔不存在。 旋转+变色
				//c在p左子树,g右单旋+g,p变色
				//g
			  // p  u
			 //c
				if (cur == Parent->_left)
				{
					RotateR(grandfather);
					Parent->_col = BLACK;
					grandfather->_col = RED;
			    }
				else
				{//c在p右子树,g-p-c双旋+变色
					//g
		          // p  u
		           // c
					RotateL(Parent);
					RotateR(grandfather);
					cur->_col = BLACK;
					grandfather->_col = RED;
				}
				break;//此处作为子树根节点的是Parent,它是黑色的,所以不用管上面是红是黑,全部符合规则,不需要再向上调整。
			}
		}
		else
		{//叔叔在左
			Node* uncle = grandfather->_left;
		//g
	   //u p
			//叔叔存在且为红
			if (uncle && uncle->_col == RED)
			{
				uncle->_col = Parent->_col = BLACK;
				grandfather->_col = RED;
				//向上调整
				if (grandfather->_col == RED)
				{
					cur = grandfather;
					Parent = cur->_parent;
				}
			}
			else
			{//叔叔不存在或存在但为黑
				//g
			  // u p
				//  c
				//单旋
				if (Parent->_right = cur)
				{
					RotateL(grandfather);
					Parent->_col = BLACK;
					grandfather->_col = RED;
				}
				else
				{
                  //g
				// u  p
				//   c
		        //双旋
					RotateR(Parent);
					RotateL(grandfather);
					cur->_col = BLACK;
					grandfather->_col = RED;
				}
				break;
			}
		}
	}
	//parent为空,也就是cur走到根节点的位置,并且根节点为红色。
	//直接给_root改色即可,反正把它改成黑色,每个支路上黑节点总数都加1,还是相同的。
	//即便根本来就是黑色,再让它成黑色也没问题。
	_root->_col = BLACK;
	return true;
}

3.红黑树验证

注:

1.代码中,blackNum作为一个形参出现,每一次递归遍历时,它的值都隶属于当下,因此递归返回时,每一个blackNum对应的是当下的每一个节点。

2.构造参考值,此处以最左侧路径的黑节点数量作参考值:

3.前序遍历⾛到空时,意味着⼀条路径⾛完了,此时进入if语句,blackNum与参考值refNum进行比较。

4.前序遍历的return用&&的妙处在于,&&前假如是false,就可直接截断,无需浪费精力去管其后面的东西。

递归展开图:

完整代码:

复制代码
bool Check(Node* root, int blackNum, const int refNum)
{
	if (root == nullptr)
	{
		// 前序遍历⾛到空时,意味着⼀条路径⾛完了

			//cout << blackNum << endl;
			if (refNum != blackNum)
			{
				cout << "存在⿊⾊结点的数量不相等的路径" << endl;
					return false;
			}
		return true;
	}
	// 检查孩⼦不太⽅便,因为孩⼦有两个,且不⼀定存在,反过来检查⽗亲就⽅便多了

		if (root->_col == RED && root->_parent->_col == RED)
		{
			cout << root->_kv.first << "存在连续的红⾊结点" << endl;
				return false;
		}
	if (root->_col == BLACK)
	{
		blackNum++;
	}
	return Check(root->_left, blackNum, refNum)
		&& Check(root->_right, blackNum, refNum);
}
bool IsBalance()
{
	if (_root == nullptr)
		return true;
	if (_root->_col == RED)
		return false;
	// 参考值

		int refNum = 0;
	Node* cur = _root;
	while (cur)
	{
		if (cur->_col == BLACK)
		{
			++refNum;
		}
		cur = cur->_left;
	}
	return Check(_root, 0, refNum);
}
相关推荐
YL200404261 小时前
041二叉树的层序遍历
数据结构·leetcode·bfs
谙弆悕博士1 小时前
【附C语言源码】C语言 栈结构 实现及其扩展操作
c语言·开发语言·数据结构·算法·链表·指针·
YuanDaima20481 小时前
图论基础原理与题目说明
数据结构·人工智能·python·算法·图论·手撕代码
z200509301 小时前
今日算法(二叉树)
数据结构
小赵不会秃头1 小时前
数据结构Day 06:线性结构、库操作及 Makefile 完整学习笔记
java·linux·数据结构·算法·面试
郝学胜-神的一滴2 小时前
干货版《算法导论》04:渐近复杂度与序列接口实战
java·开发语言·数据结构·c++·python·算法
curry____3032 小时前
二叉树的前序中序遍历以及用前序和中序求解树
数据结构
科技快报2 小时前
腾讯地图上线骑手模式 助力解决骑手配送“最后100米”难题
数据结构
木子墨5162 小时前
系统设计面试 | 实现一个限流器:滑动窗口 → 令牌桶 → 漏桶
java·开发语言·数据结构·数据库·面试·职场和发展