【C++进阶】红黑树实现

红黑树的概念

红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或
Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路
径会比其他路径长出俩倍,因而是接近平衡的。

那么有了AVL树,为什么还需要红黑树呢?

因为AVL树的平衡性更强,查找效率高,但是在插入时要进行更多的旋转,所以插入和删除时的效率更低,但是红黑树相比较AVL树的平衡性稍弱,只需要满足最长路径不超过最短路径的二倍,所以在插入删除时效率更高,只是在查找时稍慢一点,所以AVL树更适合用于对插入删除操作次数不多,需要经常进行查找的情况,而红黑树用去插入和删除次数较多的情况。

在这里插入图片描述

红黑树的性质

刚才前边提到了红黑树必须满足最长路径节点数不超过最短路径结点树的二倍,那么我们怎么样才能满足呢,其实只要满足以下红黑树的性质就可以了。

  1. 每个结点不是红色就是黑色
  2. 根节点是黑色的
  3. 如果一个节点是红色的,则它的两个孩子结点是黑色的
  4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点
  5. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)

思考:为什么满足上面的性质,红黑树就能保证:其最长路径中节点个数不会超过最短路径节点个数的两倍?

我们来分析一下这些性质,每个结点不是黑色就是红色,说明结点只有两种情况,而根结点为黑色也跟第五条性质对应,当为空树时,空节点也是黑色的,而一个节点为红色,他的子节点一定为黑色说明没有两个连续的红色结点,并且每条路径的黑色结点树相同,那么就说明一条路径上的红色节点数一定小于等于黑色结点数,则说明最长路径上的结点一定不超过最短路径的二倍。

在这里插入图片描述

红黑树节点的定义

由于节点的颜色不是红色就是黑色,所以采用枚举类型来定义结点的颜色。

枚举类型快速讲解:

在C++中,enum是一种枚举类型,用于定义一组命名的整型常量。枚举类型可以使代码更加具有可读性。可以使用enum关键字来定义枚举类型,如下所示:
enum Color {
RED,
GREEN,
BLUE
};
在上面的例子中,我们定义了一个名为Color的枚举类型,其中包含三种颜色:RED,GREEN和BLUE,它们的值分别为0,1和2。在枚举类型中,每个枚举值都可以用作整数,并且可以通过枚举名称来访问它们。
可以使用枚举类型来代替整数常量,这样可以使代码更加可读性高,而且更加易于维护。例如,下面的代码使用枚举类型来表示颜色:
Color color = RED;
if (color == GREEN) {
// do something
} else if (color == RED) {
// do something else
}
在上面的代码中,我们使用枚举类型来表示颜色,使代码更加易于理解。

并且为了方便找到父节点,所以使用三叉链的形式

下边是红黑树节点的实现

enum COLOUR
{
	RED,
	BLACK
};

template<class K,class V>
class RBtreeNode
{
public:
	RBtreeNode(const pair<K,V>& kv)
		:_kv(kv)
		,_col(RED)
		,_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
	{}
	COLOUR _col;
	RBtreeNode<K, V>* _left;
	RBtreeNode<K, V>* _right;
	RBtreeNode<K, V>* _parent;
	pair<K, V> _kv;
};

思考:那么为什么要把节点的颜色初始化为红色呢?

事实上,不论把结点的颜色设置为黑色还是红色,都可能会违反红黑树的性质,但是插入黑色节点就违背了每条路径上黑色结点树相同的性质,而插入红色节点就可能违背有两个相同红色节点的性质,但是我们比较代价,发现插入红色节点的代价更小。

红黑树结构

为了后续实现关联式容器简单,红黑树的实现中增加一个头结点,因为跟节点必须为黑色,为了
与根节点进行区分,将头结点给成黑色,并且让头结点的 pParent 域指向红黑树的根节点,pLeft
域指向红黑树中最小的节点,_pRight域指向红黑树中最大的节点。

template<class K,class V>
class RBtree
{
public:
	typedef RBtreeNode<K, V> Node;
private:
	RBtreeNode<K,V>* _root = nullptr;
};

红黑树的插入

由于红黑树本质上还是一个二叉搜索树,所以我们还是要进行第一步,就是先把节点通过二叉搜索树的规则插入,然后根据红黑树的性质进行旋转和变色。

按照二叉搜索的树规则插入新节点

二叉搜索树的插入在这里就不多赘述,可以参考前边的几篇文章,直接给出代码:

template<class K,class V>
class RBtreeNode
{
public:
	RBtreeNode(const pair<K,V>& kv)
		:_kv(kv)
		,_col(RED)
		,_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
	{}
	COLOUR _col;
	RBtreeNode<K, V>* _left;
	RBtreeNode<K, V>* _right;
	RBtreeNode<K, V>* _parent;
	pair<K, V> _kv;
};

template<class K,class V>
class RBtree
{
public:
	typedef RBtreeNode<K, V> Node;
	bool insert(const pair<K, V>& kv)
	{
		if (_root == nullptr)
		{
			_root = new Node(kv);
			_root->_col = BLACK;
			return true;
		}
		Node* cur = _root;
		Node* parent = nullptr;
		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);
		if (parent->_kv.first < kv.first)
		{
			parent->_right = cur;
		}
		else if (parent->_kv.first > kv.first)
		{
			parent->_left = cur;
		}
		cur->_parent = parent;
private:
	RBtreeNode<K,V>* _root = nullptr;
};

进行旋转和变色

步骤一:如果结点插入之后,父结点为黑色,那么就不需要进行调整,如果插入后的父结点为红色,就必须进行调整。
在这里插入图片描述
当在出c,d,e处插入时,就不需要进行调整,如果在a,b处进行插入,就必须进行调整。

步骤二:判断父结点是祖父结点的左子树还是右子树

如果父结点在祖父结点的左边,那么说明叔叔节点就是祖父节点的右边,如果父结点在祖父结点的右边,那么说明叔叔节点就是组父结点的左边。

在弄清楚叔叔节点的情况之后,那么我们就可以把调整分为三种情况:

约定:cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点

情况一:cur为红,p为红,g为黑,u存在且为红
在这里插入图片描述
如果是这种情况,那么不需要进行旋转,直接变色即可,将父节点变为黑色,祖父结点变为红色,如果祖父节点为根节点,那么就把祖父节点也变为黑色。
在这里插入图片描述
如果两个红色节点连在一起,就必须继续向上调整,所以情况一在变色完之后,要将p节点重新给cur,继续判断是否要进行调整。
在这里插入图片描述
情况二:cur为红,p为红,g为黑,u不存在/u存在且为黑,且p,g,cur节点为一条直线

此时的cur结点,p结点,g节点是三个节点为一条线,只需要进行左右单旋后进行变色即可。
在这里插入图片描述

情况三:cur为红,p为红,g为黑,u不存在/u存在且为黑,且p,g,cur节点为一条折线

当p节点,g节点,cur节点为一条折线时,先对p节点进行旋转,再对g结点旋转,旋转完之后,将g节点变为红色,cur节点变为黑色。
在这里插入图片描述


最后,当父结点为祖父的左节点或者为祖父的右节点时,旋转的方向不同,但是过程都是相同的。

源码

	bool insert(const pair<K, V>& kv)
	{
		if (_root == nullptr)
		{
			_root = new Node(kv);
			_root->_col = BLACK;
			return true;
		}
		Node* cur = _root;
		Node* parent = nullptr;
		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);
		if (parent->_kv.first < kv.first)
		{
			parent->_right = cur;
		}
		else if (parent->_kv.first > kv.first)
		{
			parent->_left = cur;
		}
		cur->_parent = parent;

		//此时父结点为存在且为红,说明祖父结点一定存在且为黑
		while (parent && parent->_col == RED)
		{
			Node* grandfater = parent->_parent;
			assert(grandfater);
			assert(grandfater->_col == BLACK);
			//父节点为祖父结点的左节点
			if (parent == grandfater->_left)
			{
				Node* uncle = grandfater->_right;

				//叔叔存在且叔叔为红色,第一种情况直接变色即可
				if (uncle && uncle->_col == RED)
				{
					parent->_col = uncle->_col = BLACK;
					grandfater->_col = RED;

					//继续向上判断
					cur = grandfater;
					parent = cur->_parent;
				}

				//叔叔不存在或者叔叔为黑色,二三种情况
				else
				{
					//判断子节点为父结点的左还是右
					//此处的两个关系为一条直线,所以属于第二种情况,进行单旋后变色
					if (cur == parent->_left)
					{
						RotateR(grandfater);
						grandfater->_col = RED;
						parent->_col = BLACK;
					}
					else
					{
						RotateL(parent);
						RotateR(grandfater);

						cur->_col = BLACK;
						grandfater->_col = RED;
					}

					break;
				}
			}
			//父节点为祖父节点的右节点
			else
			{
				Node* uncle = grandfater->_left;
				//叔叔存在且叔叔为红色,第一种情况直接变色即可
				if (uncle && uncle->_col == RED)
				{
					parent->_col = uncle->_col = BLACK;
					grandfater->_col = RED;

					//继续向上判断
					cur = grandfater;
					parent = cur->_parent;
				}

				//叔叔不存在或者叔叔为黑色,二三种情况
				else
				{
					//判断子节点为父结点的左还是右
					//此处的两个关系为一条直线,所以属于第二种情况,进行单旋后变色
					if (cur == parent->_right)
					{
						RotateL(grandfater);
						grandfater->_col = RED;
						parent->_col = BLACK;
					}
					else
					{
						RotateR(parent);
						RotateL(grandfater);

						cur->_col = BLACK;
						grandfater->_col = RED;
					}

					break;
				}
			}
		}

		_root->_col = BLACK;
		return true;
	}
	
void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		Node* pparent = parent->_parent;

		parent->_parent = subL;
		subL->_right = parent;

		parent->_left = subLR;
		if (subLR)
			subLR->_parent = parent;

		if (_root == parent)
		{
			_root = subL;
			subL->_parent = nullptr;
		}
		else
		{
			if (parent == pparent->_left)
			{
				pparent->_left = subL;
			}
			else
			{
				pparent->_right = subL;
			}
			subL->_parent = pparent;
		}
	}
	void RotateL(Node* parent)
	{
		//左旋parent,先保存parent的右节点
		Node* subR = parent->_right;
		//保存右节点的左节点
		Node* subRL = subR->_left;

		Node* pparent = parent->_parent;

		parent->_parent = subR;
		subR->_left = parent;
		parent->_right = subRL;
		if (subRL)
			subRL->_parent = parent;

		if (_root == parent)
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else
		{
			if (parent == pparent->_left)
			{
				pparent->_left = subR;
			}
			else
			{
				pparent->_right = subR;
			}
			subR->_parent = pparent;
		}
	}

红黑树的验证

红黑树的检测分为两步:

  1. 检测其是否满足二叉搜索树(中序遍历是否为有序序列)
  2. 检测其是否满足红黑树的性质

中序遍历判断是否满足二叉搜索树

	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}
	
	void _InOrder(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}
		_InOrder(root->_left);
		cout << root->_kv.first << ":" << root->_kv.second << endl;
		_InOrder(root->_right);
	}

在这里插入图片描述

判断是否满足红黑树

	bool IsBalance()
	{
		if (_root == nullptr)
		{
			return true;
		}
		if (_root->_col == RED)
		{
			cout << "根节点不是黑色的" << endl;
			return false;
		}
		int benchmark = 0;
		Node* cur = _root;
		while (cur)
		{
			if(cur->_col==BLACK)
				benchmark++;
			cur = cur->_left;
		}
		return PrevCheck(_root,benchmark,0);
	}
	
	bool PrevCheck(Node* cur,int& benchmark,int blackNum)
	{
		if (cur == nullptr)
		{
			if (blackNum != benchmark)
			{
				cout << "黑色结点的数量不相等" << endl;
				return false;
			}
			else
			{
				return true;
			}
		}
		if (cur->_col == BLACK)
		{
			blackNum++;
		}
		if (cur->_col == RED && cur->_parent->_col==RED)
		{
			cout << "存在两个连续的红节点" << endl;
			return false;
		}
		return PrevCheck(cur->_left, benchmark, blackNum)
			&& PrevCheck(cur->_right, benchmark, blackNum);
	}

在这里插入图片描述

完整源码

RBTree.h

#pragma once
#include<iostream>
#include<assert.h>
using namespace std;

enum COLOUR
{
	RED,
	BLACK
};

template<class K,class V>
class RBtreeNode
{
public:
	RBtreeNode(const pair<K,V>& kv)
		:_kv(kv)
		,_col(RED)
		,_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
	{}
	COLOUR _col;
	RBtreeNode<K, V>* _left;
	RBtreeNode<K, V>* _right;
	RBtreeNode<K, V>* _parent;
	pair<K, V> _kv;
};

template<class K,class V>
class RBtree
{
public:
	typedef RBtreeNode<K, V> Node;
	bool insert(const pair<K, V>& kv)
	{
		if (_root == nullptr)
		{
			_root = new Node(kv);
			_root->_col = BLACK;
			return true;
		}
		Node* cur = _root;
		Node* parent = nullptr;
		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);
		if (parent->_kv.first < kv.first)
		{
			parent->_right = cur;
		}
		else if (parent->_kv.first > kv.first)
		{
			parent->_left = cur;
		}
		cur->_parent = parent;

		//此时父结点为存在且为红,说明祖父结点一定存在且为黑
		while (parent && parent->_col == RED)
		{
			Node* grandfater = parent->_parent;
			assert(grandfater);
			assert(grandfater->_col == BLACK);
			//父节点为祖父结点的左节点
			if (parent == grandfater->_left)
			{
				Node* uncle = grandfater->_right;

				//叔叔存在且叔叔为红色,第一种情况直接变色即可
				if (uncle && uncle->_col == RED)
				{
					parent->_col = uncle->_col = BLACK;
					grandfater->_col = RED;

					//继续向上判断
					cur = grandfater;
					parent = cur->_parent;
				}

				//叔叔不存在或者叔叔为黑色,二三种情况
				else
				{
					//判断子节点为父结点的左还是右
					//此处的两个关系为一条直线,所以属于第二种情况,进行单旋后变色
					if (cur == parent->_left)
					{
						RotateR(grandfater);
						grandfater->_col = RED;
						parent->_col = BLACK;
					}
					else
					{
						RotateL(parent);
						RotateR(grandfater);

						cur->_col = BLACK;
						grandfater->_col = RED;
					}

					break;
				}
			}
			//父节点为祖父节点的右节点
			else
			{
				Node* uncle = grandfater->_left;
				//叔叔存在且叔叔为红色,第一种情况直接变色即可
				if (uncle && uncle->_col == RED)
				{
					parent->_col = uncle->_col = BLACK;
					grandfater->_col = RED;

					//继续向上判断
					cur = grandfater;
					parent = cur->_parent;
				}

				//叔叔不存在或者叔叔为黑色,二三种情况
				else
				{
					//判断子节点为父结点的左还是右
					//此处的两个关系为一条直线,所以属于第二种情况,进行单旋后变色
					if (cur == parent->_right)
					{
						RotateL(grandfater);
						grandfater->_col = RED;
						parent->_col = BLACK;
					}
					else
					{
						RotateR(parent);
						RotateL(grandfater);

						cur->_col = BLACK;
						grandfater->_col = RED;
					}

					break;
				}
			}
		}

		_root->_col = BLACK;
		return true;
	}

	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}
	bool IsBalance()
	{
		if (_root == nullptr)
		{
			return true;
		}
		if (_root->_col == RED)
		{
			cout << "根节点不是黑色的" << endl;
			return false;
		}
		int benchmark = 0;
		Node* cur = _root;
		while (cur)
		{
			if(cur->_col==BLACK)
				benchmark++;
			cur = cur->_left;
		}
		return PrevCheck(_root,benchmark,0);
	}
private:
	bool PrevCheck(Node* cur,int& benchmark,int blackNum)
	{
		if (cur == nullptr)
		{
			if (blackNum != benchmark)
			{
				cout << "黑色结点的数量不相等" << endl;
				return false;
			}
			else
			{
				return true;
			}
		}
		if (cur->_col == BLACK)
		{
			blackNum++;
		}
		if (cur->_col == RED && cur->_parent->_col==RED)
		{
			cout << "存在两个连续的红节点" << endl;
			return false;
		}
		return PrevCheck(cur->_left, benchmark, blackNum)
			&& PrevCheck(cur->_right, benchmark, blackNum);
	}
	void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		Node* pparent = parent->_parent;

		parent->_parent = subL;
		subL->_right = parent;

		parent->_left = subLR;
		if (subLR)
			subLR->_parent = parent;

		if (_root == parent)
		{
			_root = subL;
			subL->_parent = nullptr;
		}
		else
		{
			if (parent == pparent->_left)
			{
				pparent->_left = subL;
			}
			else
			{
				pparent->_right = subL;
			}
			subL->_parent = pparent;
		}
	}
	void RotateL(Node* parent)
	{
		//左旋parent,先保存parent的右节点
		Node* subR = parent->_right;
		//保存右节点的左节点
		Node* subRL = subR->_left;

		Node* pparent = parent->_parent;

		parent->_parent = subR;
		subR->_left = parent;
		parent->_right = subRL;
		if (subRL)
			subRL->_parent = parent;

		if (_root == parent)
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else
		{
			if (parent == pparent->_left)
			{
				pparent->_left = subR;
			}
			else
			{
				pparent->_right = subR;
			}
			subR->_parent = pparent;
		}
	}
	void _InOrder(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}

		_InOrder(root->_left);
		cout << root->_kv.first << ":" << root->_kv.second << endl;
		_InOrder(root->_right);
	}
private:
	RBtreeNode<K,V>* _root = nullptr;
};

void TestRBtree()
{
	int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };  // 测试双旋平衡因子调节
	//int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
	RBtree<int, int> t1;
	for (auto e : a)
	{
		t1.insert(make_pair(e, e));
	}
	t1.InOrder();
}
void TestRBtree2()
{
	size_t N = 1000;
	srand(time(0));
	RBtree<int, int> t1;
	for (size_t i = 0; i < N; ++i)
	{
		int x = rand();
		cout << "Insert:" << x << ":" << i << endl;
		t1.insert(make_pair(x, i));
	}
	cout << "IsBalance:" << t1.IsBalance() << endl;
}

test.cpp

#include"RBtree.h"
using namespace std;
int main()
{
	TestRBtree2();
	return 0;
}
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

清扰077

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

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

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

打赏作者

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

抵扣说明:

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

余额充值