数据结构之红黑树(超详细版+通俗易懂)

前言:在前面一篇文章我给老铁们分享了AVL树插入的实现,我们知道AVL树底层也是二叉搜索树,只不过是在二叉搜索树的基础上加入平衡因子,从而通过控制平衡因子让树达到平衡,AVL树要求左右子树绝对平衡(左右子树的高度差绝对值不超过1),因此需要旋转的代价也比较大,那么今天我们来分享一种和AVL树效率差不多的,但是没有AVL旋转的代价那么大的树,红黑树。

1.红黑树定义:红黑树是在二叉搜索树的基础上,再给每一个结点加入颜色,颜色要么是黑色要么是红色,红黑树的名称也是由此而来。

红黑树图片
在这里插入图片描述

2.红黑树性质:(1)根结点是黑色结点;(2)每条路径的黑结点个数相同;(3)不能有连续的红结点 (4)如果一个结点是红色的,那么它的两个孩子结点一定是黑色的 (5)每一个叶子结点都是黑色的(6)红黑树要求最长路径结点个数不超过最短路径结点个数的两倍

在这里插入图片描述

那么为什么满足前面五个性质就能一定满足第六个性质呢?

我们知道根结点一定是黑色结点,如果树不为空的情况下,那么最短路劲就是只有一个根结点,最长路劲是有红黑结点交替出现,由于结点是红的话,那么它的两个孩子一定是黑结点,所以每条路径上红节点的个数最多等于黑结点的个数,由此可见最长路径结点个数一定不超过最短路径结点个数的两倍。

如果上面的文字说明没听懂,那就举个简单的例子。

假设有一个红黑树,从根到叶子结点的黑结点的个数为3,那么该红黑树最短路径就是3,那么最长路径就是红黑结点交替出现,就相当于红结点黑结点3,所以最长路劲结点个数一定不会超过最短路劲结点个数的两倍。

3.红黑树定义结点(使用三叉链进行定义)

上面我们已经分享完了红黑树的定义和红黑树的性质,那么我们接下来就实践一下吧,定义出红黑树的结点。

要定义红黑树,那么我们就需要明白树中有什么,树中得有指向左右子树的结点和指向父结点的指针,还有数据,还有结点的颜色,那么结点的颜色我们直接使用c语言的枚举类型进行定义。

enum Colour
{
	RED,
	BLACK,
};

template<class K,class V>
struct RBTreeNode//把结点定义为公有的
{
	RBTreeNode* _left;
	RBTreeNode* _right;
	RBTreeNode* _parent;

	pair<K, V> _kv;//数据有key和val

	Colour _col;//颜色

	//写结点的构造函数,new函数需要调用结点的构造函数
	RBTreeNode(const pair<K, V>& kv)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _kv(kv)
		, _col(RED)//颜色先给为红色
	{};
};

那么为啥需要把红色设置为默认颜色呢?
为了防止破坏每一个路径的黑结点个数相同的性质

4.红黑树的插入(重点)

红黑树插入有五种情况:

(1)我们先按二叉搜索插入规则,new出一个新的结点,然后再分类讨论插入时的各种情况。

template<class K,class V>
class RBTree
{
	typedef  RBTreeNode<K, V> Node;
public:
	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)
			{
				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和parent链接起来
			//链接到右边
			if (parent->_kv.first < kv.first)
			{
				parent->_right = cur;
			}
			else
			{
				parent->_left = cur;
			}
			//把新增结点颜色给为红色
			cur->_col = RED;
		}
	}
private:
	Node* _root = nullptr;
};

肯定有老铁好奇,为什么需要把新增设置为红色,黑色不行吗?
其实两种颜色都行,只是给成红色对树的性质破坏较小,最多只是一条路径破坏,如
果给出黑色,那么多条路径都被破坏了。

当父亲结点时是祖父结点的左边时。

(2)第一种情况:当插入红色结点时,父亲结点为红色,祖父结点为黑色,舅舅结点存在且为红色;
在这里插入图片描述

//第一种情况,当父亲结点存在且为红
while (parent&& parent->_col = RED)
{
	//祖父结点一定为黑
	Node* grandfather = parent->_parent;
	//寻找uncle结点
	//当父亲结点是左结点,那么uncle结点就是右节点
	if (parent == grandfather->_left)
	{
		Node* uncle = grandfather->_right;
		//情况1:uncle结点存在,且颜色为红色
		if (uncle&& uncle->_col = RED)
		{
			//把parent和uncle结点变黑
			parent->_col = uncle->_col = BLACK;
			//再把祖父结点变红
			grandfather->_col = RED;

			//如果祖父结点的父亲结点还是红色
			//那么继续往上处理
			cur = grandfather;
			parent = cur->_parent;
		}
	}
}

(3)第二种情况:当插入红色结点时,父亲结点为红色,祖父结点为黑色,舅舅结点不存在/存在且为黑

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

//**当cur和parent为红,uncle结点不存在**
while (parent && parent->_col == RED)
{
	//红黑树的调节关键看叔叔结点
	//找叔叔结点
	Node* grandfther = parent->_parent;
	if (grandfther->_left == parent)
	{
		Node* uncle = grandfther->_right;
		//情况1:uncle结点存在,且颜色为红色
		if (uncle && uncle->_col == RED)
		{
			//让父结点和叔叔结点变黑
			parent->_col = uncle->_col = BLACK;
			//再把祖父结点变红
			grandfther->_col = RED;

			//继续往上处理
			cur = grandfther;
			parent = cur->_parent;
		}
		//情况2:uncle不存在/uncle存在且为黑
		else
		{
			//第二种情况
			RotateR(grandfther);
			//旋转后把祖父颜色变红,把父亲颜色变黑
			grandfther->_col = RED;
			parent->_col = BLACK;
			break;
		}
	}
//左单旋
//该树是三叉链实现
void RotateL(Node* parent)
{
	//定义一个指向parent右子树的subR指针
	Node* subR = parent->_right;
	//定义一个指向subR的左边的指针
	Node* subRL = subR->_left;

	//把prent右边指向subRL
	parent->_right = subRL;

	//当sbuRL不为空时,就让subRL的prent指向prent
	if (subRL)
	{
		subRL->_parent = parent;
	}

	//让subR的左指向prent
	//再让parent的parent指向subR
	subR->_left = parent;
	//把parent的parent先保存起来,防止parent不是根节点时,让subR链接上parent的parent
	Node* ppNode = parent->_parent;
	parent->_parent = subR;

	//原来parent是节点的根,现在subR是根
	if (_root == parent)
	{
		_root = subR;
		//再让subR的根节点指向空
		subR->_parent = nullptr;
	}
	else
	{
		//如果parent原来是ppNode的左边,那么subR也指向ppNode的左边
		if (ppNode->_left == parent)
			ppNode->_left = subR;
		else
			ppNode->_right = subR;

		//再把subR的parent指向ppNode
		subR->_parent = ppNode;
	}
}
//右单旋
void RotateR(Node* parent)
{
	Node* subL = parent->_left;
	Node* subLR = subL->_right;

	//让parent的左边指向subLR
	parent->_left = subLR;

	if (subLR)
		subLR->_parent = parent;

	subL->_right = parent;
	//先保存好parent的parent
	Node* ppNode = parent->_parent;
	parent->_parent = subL;

	if (_root == parent)
	{
		_root = subL;
		subL->_parent = nullptr;
	}
	else
	{
		if (ppNode->_left == parent)
		{
			ppNode->_left = subL;
		}
		else
		{
			ppNode->_right = subL;
		}
		//让subL节点的parent指向ppNode
		subL->_parent = ppNode;
	}
}

(3)第三种情况:当插入红色结点时,父亲结点为红色,,新插入的结点在父亲结点的右子树,祖父结点为黑色,舅舅结点不存在/存在且为黑

在这里插入图片描述

while (parent && parent->_col == RED)
{
	//红黑树的调节关键看叔叔结点
	//找叔叔结点
	Node* grandfther = parent->_parent;
	if (grandfther->_left == parent)
	{
		Node* uncle = grandfther->_right;
		//情况1:uncle结点存在,且颜色为红色
		if (uncle && uncle->_col == RED)
		{
			//让父结点和叔叔结点变黑
			parent->_col = uncle->_col = BLACK;
			//再把祖父结点变红
			grandfther->_col = RED;

			//继续往上处理
			cur = grandfther;
			parent = cur->_parent;
		}
		//情况2/情况3:uncle不存在/uncle存在且为黑
		else
		{
			//cur在父亲结点右边
			//情况3:双旋
			if (cur == parent->_right)
			{
				RotateL(parent);
				swap(parent, cur);
			}
			//旋转后把祖父颜色变红,把父亲颜色变黑
			grandfther->_col = RED;
			parent->_col = BLACK;
			
			break;
		}
	}

当父亲结点时是祖父结点的右边时思路是和左边的一样,就不再重复讲解思路了,直接上代码了。

	//父亲结点在祖父结点的右边
	else
	{
		Node* uncle = grandfther->_left;
		//情况1:uncle结点存在,且颜色为红色
		if (uncle && uncle->_col == RED)
		{
			//把父亲和叔叔结点变黑
			parent->_col = uncle->_col = BLACK;
			grandfther->_col = RED;

			cur = grandfther;
			parent = cur->_parent;
		}

		else
		{
			if (cur == parent->_left)
			{
				//先右单旋
				RotateR(parent);
				swap(parent, cur);
			}

			RotateL(grandfther);
			//祖父变红,父亲变黑
			grandfther->_col = RED;
			parent->_col = BLACK;
		}
	}
}

5.红黑树插入结点的全部代码

	//插入
	bool Insert(const pair<K, V>& kv)
	{
		//1.按搜索树的规则进行插入
		if (_root == nullptr)
		{
			_root = new Node(kv);
			//根结点颜色为黑色
			_root->_col = BLACK;
			return true;
		}
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			//如果需要插入的结点的key>根结点的key,那么就往右边查找
			if (cur->_kv.first < kv.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (cur->_kv.first > kv.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			//表示该结点已经在红黑树中了
			else
			{
				return false;
			}
		}

		//new出一个结点再和父节点链接起来
		cur = new Node(kv);
		if (parent->_kv.first < kv.first)
		{
			parent->_right = cur;
			cur->_parent = parent;
		}
		else
		{
			parent->_left = cur;
			cur->_parent = parent;
		}

		//新增结点的颜色为红色(红色只是破坏一条路径)
		cur->_col = RED;
		//当当前结点和父结点都是红色
		//父亲存在
		while (parent && parent->_col == RED)
		{
			//红黑树的调节关键看叔叔结点
			//找叔叔结点
			Node* grandfther = parent->_parent;
			if (grandfther->_left == parent)
			{
				Node* uncle = grandfther->_right;
				//情况1:uncle结点存在,且颜色为红色
				if (uncle && uncle->_col == RED)
				{
					//让父结点和叔叔结点变黑
					parent->_col = uncle->_col = BLACK;
					//再把祖父结点变红
					grandfther->_col = RED;

					//继续往上处理
					cur = grandfther;
					parent = cur->_parent;
				}
				//情况2/情况3:uncle不存在/uncle存在且为黑
				else
				{
					//cur在父亲结点右边
					//情况3:双旋
					if (cur == parent->_right)
					{
						RotateL(parent);
						swap(parent, cur);
					}

					//第二种情况(注意:第二种情况有可能是第三种情况变过来的)
					RotateR(grandfther);
					//旋转后把祖父颜色变红,把父亲颜色变黑
					grandfther->_col = RED;
					parent->_col = BLACK;

					break;
				}
			}
			//父亲结点在祖父结点的右边
			else
			{
				Node* uncle = grandfther->_left;
				//情况1:uncle结点存在,且颜色为红色
				if (uncle && uncle->_col == RED)
				{
					//把父亲和叔叔结点变黑
					parent->_col = uncle->_col = BLACK;
					grandfther->_col = RED;

					cur = grandfther;
					parent = cur->_parent;
				}

				else
				{
					if (cur == parent->_left)
					{
						//先右单旋
						RotateR(parent);
						swap(parent, cur);
					}

					RotateL(grandfther);
					//祖父变红,父亲变黑
					grandfther->_col = RED;
					parent->_col = BLACK;
				}
			}
		}
		//把根结点变黑
		_root->_col = BLACK;

		return true;
	}

6.红黑树结点的查找

由于红黑树的底层还是二叉搜索树,所以查找结点还是按二叉搜索树查找结点的方法进行查找,这里也不再讲述思路了(不懂可以去看看二叉搜索树那篇博客),直接上代码了。

	Node* Find(const K& key)
	{
		Node* cur = _root;
		while (cur)
		{
			if (cur->_kv.first < key)
			{
				cur = cur->_right;
			}
			else if (cur->_kv.first > key)
			{
				cur = cur->_left;
			}
			else
			{
				return cur;
			}
		}
		return nullptr;
	}

测试红黑树

前面我们已经分享完了红黑树的插入和查找操作,那么我们来验证一下吧!

//1.先检验是否满足二叉搜索树

void _InOrder(Node* root)
{
	if (root == nullptr)
	{
		return;
	}

	//中序递归遍历
	//先递归左树,再递归右树
	_InOrder(root->_left);
	cout << root->_kv.first << ":" << root->_kv.second << endl;
	_InOrder(root->_right);
}

void InOrder()
{
	_InOrder(_root);
	cout << endl;
}

void TestRBTree()
{
	int a[] = { 16,3,7,11,9,26,18,14,15 };
	RBTree<int, int> t;
	for (auto e : a)
	{
		t.Insert(make_pair(e, e));
	}
	t.InOrder();
}
int main()
{
	TestRBTree();
	return 0;
}

在这里插入图片描述

再检验是否满足红黑树性质


	bool IsValidRBTree()
	{
		Node* pRoot = _root;
		// 空树也是红黑树
		if (nullptr == pRoot)
			return true;
		// 检测根节点是否满足情况
		if (BLACK != pRoot->_col)
		{
			cout << "违反红黑树性质二:根节点必须为黑色" << endl;
			return false;
		}
		// 获取任意一条路径中黑色节点的个数
		size_t blackCount = 0;
		Node* pCur = pRoot;
		while (pCur)
		{
			if (BLACK == pCur->_col)
				blackCount++;
			pCur = pCur->_left;
		}
		// 检测是否满足红黑树的性质,k用来记录路径中黑色节点的个数
		size_t k = 0;
		return _IsValidRBTree(pRoot, k, blackCount);
	}
	bool _IsValidRBTree(Node* pRoot, size_t k, const size_t blackCount)
	{
		//走到null之后,判断k和black是否相等
		if (nullptr == pRoot)
		{
			if (k != blackCount)
			{
				cout << "违反性质四:每条路径中黑色节点的个数必须相同" << endl;
				return false;
			}
			return true;
		}
		// 统计黑色节点的个数
		if (BLACK == pRoot->_col)
			k++;
		// 检测当前节点与其双亲是否都为红色
		Node* parent = pRoot->_parent;
		if (parent && RED == parent->_col && RED == pRoot->_col)
		{
			cout << "违反性质三:没有连在一起的红色节点" << endl;
			return false;
		}
		return _IsValidRBTree(pRoot->_left, k, blackCount) &&
			_IsValidRBTree(pRoot->_right, k, blackCount);
	}
void TestRBTree()
{
	int a[] = { 16,3,7,11,9,26,18,14,15 };
	RBTree<int, int> t;
	for (auto e : a)
	{
		t.Insert(make_pair(e, e));
	}
	t.InOrder();
	cout<<"该红黑树是否满足性质:" << t.IsValidRBTree() << endl;
}

int main()
{
	TestRBTree();
	return 0;
}

在这里插入图片描述

总结:

红黑树插入和查找分享完了,红黑树的删除就不在这里分享了,感兴趣的老铁可以去看这篇博客:红黑树的删除

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

平平无奇。。。

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值