【数据结构】红黑树(C++实现)

这篇具有很好参考价值的文章主要介绍了【数据结构】红黑树(C++实现)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

【数据结构】红黑树(C++实现),数据结构,数据结构

👀樊梓慕:个人主页

 🎥个人专栏:《C语言》《数据结构》《蓝桥杯试题》《LeetCode刷题笔记》《实训项目》《C++》《Linux》《算法》

🌝每一个不曾起舞的日子,都是对生命的辜负


目录

前言

1.概念

2.性质

3.节点的定义 

4.插入

4.1情况1:叔叔u存在且为红

4.2情况2:叔叔u不存在或者叔叔u存在且为黑

4.3代码实现

5.验证

6.红黑树完整源码

7.AVL树与红黑树的性能比较


前言

如果没有现在的红黑树的话,那么可能set与map底层的数据结构就是AVL树了,那么红黑树的设计为什么能够取代AVL树的地位呢,红黑树的设计又有哪些奥秘,今天让我们一同来探索一下吧!


欢迎大家📂收藏📂以便未来做题时可以快速找到思路,巧妙的方法可以事半功倍。 

=========================================================================文章来源地址https://www.toymoban.com/news/detail-839214.html

GITEE相关代码:🌟樊飞 (fanfei_c) - Gitee.com🌟

=========================================================================


1.概念

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

红黑树不像AVL树那样绝对的平衡,而是接近平衡,

但是这个影响是常数级别的,接着往下看。

【数据结构】红黑树(C++实现),数据结构,数据结构


2.性质

了解了红黑树的性质,你就明白了为什么红黑树是如何控制平衡的。

  • 每个结点不是红色就是黑色
  • 根节点是黑色的
  • 如果一个节点是红色的,则它的两个孩子结点必须是黑色的(没有连续的红色节点)。
  • 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点。
  • 每个叶子结点都是黑色的(此处的叶子结点指的是空结点-NIL节点,因为路径指的是从根节点走到空,而不是从根节点走到叶子节点,所以设计者专门将空节点定义为NIL节点默认为黑色,就是为了防止人们数错路径)。

接下来我们构建极端场景来分析下为什么最长路径中节点的个数不会超过最短路径节点个数的两倍?

  • 最短路径情况:节点为全黑。
  • 最长路径情况:一黑一红排列。
  • 大前提:每条路径都包含相同数量的黑色节点。

举例:

【数据结构】红黑树(C++实现),数据结构,数据结构

而且红黑树不是每次搜索都是2*logN。 


 AVL树与红黑树搜索效率基本平齐,但是AVL树实现更小的高度的方法是频繁地旋转,而旋转的开销还是比较大的,所以AVL树有点『 得不偿失』。

红黑树虽然也需要旋转来控制高度,但是引入了颜色控制,可以减少旋转的次数。


3.节点的定义 

enum Colour
{
	RED,
	BLACK
};

template<class K, class V>
struct RBTreeNode
{
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _parent;
	pair<K, V> _kv;
	Colour _col;

	RBTreeNode(const pair<K, V>& kv)
		: _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _kv(kv)
		, _col(RED)
	{}
};

思考:节点的默认颜色为什么是红色? 

因为如果插入黑色节点就一定会破坏规则(每条路径上的黑色节点数相同),并且会影响其他路径。

所以我们需要设置节点的默认颜色为红色


4.插入

因为新节点的默认颜色是红色,因此

  • 如果其双亲节点的颜色是黑色,没有违反红黑树任何性质,则不需要调整;
  • 但当新插入节点的双亲节点颜色为红色时,就违反了性质三不能有连在一起的红色节点,此时需要对红黑树分情况来讨论:

所以以下都是当新插入节点的父节点为红色时的情况: 

4.1情况1:叔叔u存在且为红

【数据结构】红黑树(C++实现),数据结构,数据结构

 g的颜色是确定为黑的,因为如果g不为黑,那么就证明上一层就已经不满足红黑树的规则了。

此时c一定是新插入的节点,并且已经破坏了没有连续的红色节点的规则,所以我们需要进行调整。

调整的办法:p/u变黑,g变红。

【数据结构】红黑树(C++实现),数据结构,数据结构

 g变红的目的是为了保持当前分支黑色节点的数目不变。

但这里注意:

  • 如果g为根节点,那么需要再将g变黑(规则:根节点必须是黑色),即将所有路径的黑色节点数都+1,仍然符合规则。
  • 如果g不为根节点,那么g为红色,此时将g视为cur,继续向上调整。

4.2情况2:叔叔u不存在或者叔叔u存在且为黑

【数据结构】红黑树(C++实现),数据结构,数据结构

 同样的g的颜色是确定为黑的,因为如果g不为黑,那么就证明上一层就已经不满足红黑树的规则了。

  • 如果叔叔u不存在,那么c一定是新插入的节点,因为叔叔u不存在说明在parent的下面不可能再挂黑色结点了。
  • 如果叔叔u存在且为黑,那么c一定不是新插入的节点,因为之前已经不满足规则了,所以c一定是刚刚变红的,也就是说有可能是情况1调整过后,将c变红的。 

此时单纯地变色已经无法处理了,所以此时我们需要进行旋转。

很明显根据之前学习的AVL树,面对如上图情况时我们需要对g进行右旋。 

即当p为g的左,cur为p的左时对g进行右旋:

【数据结构】红黑树(C++实现),数据结构,数据结构

注意:之前分析由于cur是因为之前的调整才变红的,所以cur向下的路径中一定有一个黑色节点与叔叔抵消,此时达到平衡。

如果叔叔不存在也是一样的对g进行右旋即可:

【数据结构】红黑树(C++实现),数据结构,数据结构

那么假如cur在p的右子树上呢?此时则需要先左旋再右旋。

即当p为g左,cur为p的右时进行左右双旋,然后变色。

【数据结构】红黑树(C++实现),数据结构,数据结构

  同样的如果叔叔不存在也是一样的进行左右双旋,然后变色。

【数据结构】红黑树(C++实现),数据结构,数据结构

总结下来就以上这些种情况,相信你掌握了左面的这些种情况,右面也非常简单。

4.3代码实现

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); // 红色的
	if (parent->_kv.first < kv.first)
	{
		parent->_right = cur;
	}
	else
	{
		parent->_left = cur;
	}
	cur->_parent = parent;

	while (parent && parent->_col == RED)
	{
		Node* grandfather = parent->_parent;
		if (parent == grandfather->_left)
		{
			Node* uncle = grandfather->_right;
			// 情况一:叔叔存在且为红
			if (uncle && uncle->_col == RED)
			{
				// 变色
				parent->_col = uncle->_col = BLACK;
				grandfather->_col = RED;

				// 继续往上处理
				cur = grandfather;
				parent = cur->_parent;
			}
			else
			{
				// 情况二:叔叔不存在或者存在且为黑
				// 旋转+变色
				if (cur == parent->_left)
				{
					//       g
					//    p    u
					// c
					RotateR(grandfather);
					parent->_col = BLACK;
					grandfather->_col = RED;
				}
				else
				{
					//       g
					//    p     u
					//      c
					RotateL(parent);
					RotateR(grandfather);
					cur->_col = BLACK;
					grandfather->_col = RED;
				}
				break;
			}
		}
		else
		{
			Node* uncle = grandfather->_left;
			// 情况一:叔叔存在且为红
			if (uncle && uncle->_col == RED)
			{
				// 变色
				parent->_col = uncle->_col = BLACK;
				grandfather->_col = RED;

				// 继续往上处理
				cur = grandfather;
				parent = cur->_parent;
			}
			else
			{
				// 情况二:叔叔不存在或者存在且为黑
				// 旋转+变色
				//      g
				//   u     p
				//            c
				if (cur == parent->_right)
				{
					RotateL(grandfather);
					parent->_col = BLACK;
					grandfather->_col = RED;
				}
				else
				{
					//		g
					//   u     p
					//      c
					RotateR(parent);
					RotateL(grandfather);
					cur->_col = BLACK;
					grandfather->_col = RED;
				}
				break;
			}
		}
	}
	_root->_col = BLACK;

	return true;
}

void RotateL(Node* parent)
{
	Node* subR = parent->_right;
	Node* subRL = subR->_left;

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

	subR->_left = parent;
	Node* ppnode = parent->_parent;
	parent->_parent = subR;

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

void RotateR(Node* parent)
{
	Node* subL = parent->_left;
	Node* subLR = subL->_right;

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

	subL->_right = parent;

	Node* ppnode = parent->_parent;
	parent->_parent = subL;

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

5.验证

红黑树的验证分为两步:

首先要对树是否满足搜索特性进行检测,这一部分我们只需要对树进行中序遍历,观察得到的序列是否为有序序列即可。

其次就是如何判断该树满足红黑树的特性呢?

其实就是看是否满足红黑树的规则即可。

即:

  • 根节点是黑色的;
  • 没有连续的红色节点;
  • 每条路径均包含相同数目的黑色结点。

首先根节点是否为黑很好检测。

其次是否有连续的红色节点,由于我们采用的是三叉链式结构,我们维护了父亲节点,所以只需要判断当前节点和父亲节点是否同时为红即可。

最后就是如何判断每条路径的黑色节点数目是否相同,其实最简单的做法就是先统计一下最左路径的黑色节点数目,然后将该值作为参考,检验其他路径是否与该值相同即可。

bool Check(Node* cur, int blackNum, int refBlackNum)
{
	if (cur == nullptr)
	{
		if (refBlackNum != blackNum)
		{
			cout << "黑色节点的数量不相等" << endl;
			return false;
		}
		return true;
	}

	if (cur->_col == RED && cur->_parent->_col == RED)
	{
		cout << cur->_kv.first << "存在连续的红色节点" << endl;
		return false;
	}

	if (cur->_col == BLACK)
		++blackNum;

	return Check(cur->_left, blackNum, refBlackNum)
		&& Check(cur->_right, blackNum, refBlackNum);
}

bool IsBalance()
{
	if (_root && _root->_col == RED)
		return false;

	int refBlackNum = 0;
	Node* cur = _root;
	while (cur)
	{
		if (cur->_col == BLACK)
			refBlackNum++;

		cur = cur->_left;
	}
	return Check(_root, 0, refBlackNum);
}

6.红黑树完整源码

enum Colour
{
	RED,
	BLACK
};

template<class K, class V>
struct RBTreeNode
{
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _parent;
	pair<K, V> _kv;
	Colour _col;

	RBTreeNode(const pair<K, V>& kv)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _kv(kv)
		, _col(RED)
	{}
};

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); // 红色的
		if (parent->_kv.first < kv.first)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}
		cur->_parent = parent;

		while (parent && parent->_col == RED)
		{
			Node* grandfather = parent->_parent;
			if (parent == grandfather->_left)
			{
				Node* uncle = grandfather->_right;
				// 情况一:叔叔存在且为红
				if (uncle && uncle->_col == RED)
				{
					// 变色
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;

					// 继续往上处理
					cur = grandfather;
					parent = cur->_parent;
				}
				else
				{
					// 情况二:叔叔不存在或者存在且为黑
					// 旋转+变色
					if (cur == parent->_left)
					{
						//       g
						//    p    u
						// c
						RotateR(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else
					{
						//       g
						//    p     u
						//      c
						RotateL(parent);
						RotateR(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}

					break;
				}
			}
			else
			{
				Node* uncle = grandfather->_left;
				// 情况一:叔叔存在且为红
				if (uncle && uncle->_col == RED)
				{
					// 变色
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;

					// 继续往上处理
					cur = grandfather;
					parent = cur->_parent;
				}
				else
				{
					// 情况二:叔叔不存在或者存在且为黑
					// 旋转+变色
					//      g
					//   u     p
					//            c
					if (cur == parent->_right)
					{
						RotateL(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else
					{
						//		g
						//   u     p
						//      c
						RotateR(parent);
						RotateL(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}

					break;
				}
			}
		}

		_root->_col = BLACK;

		return true;
	}

	void RotateL(Node* parent)
	{
		++rotateSize;

		Node* subR = parent->_right;
		Node* subRL = subR->_left;

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

		subR->_left = parent;
		Node* ppnode = parent->_parent;
		parent->_parent = subR;

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

	void RotateR(Node* parent)
	{
		++rotateSize;

		Node* subL = parent->_left;
		Node* subLR = subL->_right;

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

		subL->_right = parent;

		Node* ppnode = parent->_parent;
		parent->_parent = subL;

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

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

		_InOrder(root->_left);
		cout << root->_kv.first << endl;
		_InOrder(root->_right);
	}

	void InOrder()
	{
		_InOrder(_root);
	}

	bool Check(Node* cur, int blackNum, int refBlackNum)
	{
		if (cur == nullptr)
		{
			if (refBlackNum != blackNum)
			{
				cout << "黑色节点的数量不相等" << endl;
				return false;
			}

			//cout << blackNum << endl;
			return true;
		}

		if (cur->_col == RED && cur->_parent->_col == RED)
		{
			cout << cur->_kv.first << "存在连续的红色节点" << endl;
			return false;
		}

		if (cur->_col == BLACK)
			++blackNum;

		return Check(cur->_left, blackNum, refBlackNum)
			&& Check(cur->_right, blackNum, refBlackNum);
	}

	bool IsBalance()
	{
		if (_root && _root->_col == RED)
			return false;

		int refBlackNum = 0;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_col == BLACK)
				refBlackNum++;

			cur = cur->_left;
		}

		return Check(_root, 0, refBlackNum);
	}

	size_t Size()
	{
		return _Size(_root);
	}

	size_t _Size(Node* root)
	{
		if (root == NULL)
			return 0;

		return _Size(root->_left)
			+ _Size(root->_right) + 1;
	}

	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 NULL;
	}

	int _Height(Node* root)
	{
		if (root == nullptr)
			return 0;

		int leftHeight = _Height(root->_left);
		int rightHeight = _Height(root->_right);

		return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
	}

	int Height()
	{
		return _Height(_root);
	}

	int GetRotateSize()
	{
		return rotateSize;
	}

private:
	Node* _root = nullptr;
	int rotateSize = 0;
};

注:rotateSize成员是用来记录旋转次数的,若不记录可以删除。 


7.AVL树与红黑树的性能比较

【数据结构】红黑树(C++实现),数据结构,数据结构 如上图,这是在一千万数据下得到的测试结果。

结论:红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是logN,红黑树不追求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对而言,红黑树降低了插入和旋转的次数,所以在经常进行增删的结构中性能比AVL树更优,而且红黑树实现比较简单,所以实际运用中红黑树更多(比如STL库中的set和map容器,linux内核等等)。而AVL树虽然在高度和搜索上优于红黑树,但基本可以忽略。


=========================================================================

如果你对该系列文章有兴趣的话,欢迎持续关注博主动态,博主会持续输出优质内容

🍎博主很需要大家的支持,你的支持是我创作的不竭动力🍎

🌟~ 点赞收藏+关注 ~🌟

=========================================================================

到了这里,关于【数据结构】红黑树(C++实现)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【高阶数据结构】红黑树 {概念及性质;红黑树的结构;红黑树的实现;红黑树插入操作详细解释;红黑树的验证}

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

    2024年02月09日
    浏览(48)
  • 【数据结构】二叉树---红黑树的实现

    目录 一.  红黑树的概念及性质 二.  红黑树结点结构的定义 三.  红黑树的插入操作      1. 情况一      2. 情况二        3. 情况三 四.  红黑树的验证 五.  红黑树与AVL树的比较 红黑树是一种自平衡的二叉搜索树,它在每个节点上增加了一个存储位来表示节点的颜色,

    2024年03月21日
    浏览(43)
  • Java 数据结构篇-实现红黑树的核心方法

      🔥博客主页: 【 小扳_-CSDN博客】 ❤感谢大家点赞👍收藏⭐评论✍      文章目录         1.0 红黑树的说明         2.0 红黑树的特性         3.0 红黑树的成员变量及其构造方法         4.0 实现红黑树的核心方法         4.1 红黑树内部类的核心方法         (1)判

    2024年01月24日
    浏览(45)
  • 数据结构 | 红黑树

    节点的左边比节点的值小,右边比节点的值大。 节点要么是 红色 ,要么是 黑色 根节点 是黑色 叶子节点都是黑色的空节点 红黑树中红色节点的子节点都是黑色 从任一节点到叶子节点的所有路径都包含相同数目的黑色节点 在添加或者删除节点的时候,如果不满足这些性质会

    2024年01月21日
    浏览(43)
  • 数据结构——红黑树

    目录 概念 性质 结点的定义  插入 调整 当p是g的左孩子时 当p为g的右孩子时 插入完整代码 红黑树的检测 红黑树完整代码(包括测试数据)   红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是RED或BLACK。 通过对任何一条从根到叶子的路径

    2023年04月09日
    浏览(45)
  • 【数据结构】红黑树

    🐱作者:一只大喵咪1201 🐱专栏:《数据结构与算法》 🔥格言: 你只管努力,剩下的交给时间! 在学习AVL树的时候,我们知道,当修改AVL树的结构(插入,删除)时,会通过旋转来保证平衡因子不超过1,所以频繁的修改结构会导致效率低下,今天我们学习的红黑树就完美解

    2023年04月17日
    浏览(48)
  • 【数据结构-树】红黑树

    💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:kuan 的首页,持续学习,不断总结,共同进步,活到老学到老 导航 檀越剑指大厂系列:全面总

    2024年02月07日
    浏览(43)
  • [数据结构]-红黑树

    前言 作者 : 小蜗牛向前冲 名言: 我可以接受失败,但我不能接受放弃   如果觉的博主的文章还不错的话,还请 点赞,收藏,关注👀支持博主。如果发现有问题的地方欢迎❀大家在评论区指正 目录 一、红黑树的基本知识  1、红黑树的概念 2、性质  二、红黑树的模拟实

    2024年02月04日
    浏览(45)
  • 红黑树数据结构

    现在JAVASE中HashMap中底层源码是由数组+链表+红黑树进行设计的,然后很多地方也是用到红黑树,这里单独对红黑树数据结构进行简单的介绍。 目录 红黑树概念 红黑树的性质 自平衡规则 代码   红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可

    2024年02月01日
    浏览(39)
  • 数据结构篇十:红黑树

      红黑树是解决单支树问题的另一种解决方法,它相比较AVL树减少了调整的次数,AVL是一格绝对平衡的树,而红黑树只要求最长路径不超过最短路径的二倍,相比较大大减少了调整次数。在实际中更多的也是使用红黑树,就比如后面的map和set,我们就是以红黑树进行封装的

    2024年03月12日
    浏览(51)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包