【C++】红黑树

红黑树

1、红黑树的概念

红黑树是一棵二叉搜索树,他的每个节点增加一个存储位来表示节点的颜色,可以是红色或者黑色。通过对任何⼀条从根到叶子的路径上各个节点的颜色进行约束,红黑树确保没有一条路径会比其他路径长出2倍,因而是接近平衡的

2、红黑树的性质

  1. 根节点是黑色
  2. 如果有一个节点是红色的,则它的两个孩子必须是黑色的
  3. 不存在连续的两个红色节点
  4. 对于每个节点,从该节点到其所有后代节点的简单路径上,均包含相同数目的黑色节点

如何保证最长路径不会超过最短路径的两倍?

从根到NULL结点的每条路径都有相同数量的黑色结点,所以极端场景下,最短路径就就是全是黑色节点的路径,假设最短路径长度为bh

任意一条路径不会有连续的红色结点,所以极端场景下,最长的路径就是一

黑一红间隔组成,那么最长路径的长度为2*bh

因此,最长路径一定是一黑一红的排列,最短路径全为黑节点,任意一条路径上的黑色节点的占比一定是大于等于1/2的

3、红黑树节点的定义

cpp 复制代码
// 枚举值表示颜色
enum Colour
{
	RED,
	BLACK
};

// 这里我们默认按key/value结构实现
template<class K, class V>
struct RBTreeNode
{
	// 这里更新控制平衡也要加入parent指针
	pair<K, V> _kv;
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _parent;
	Colour _col;
	RBTreeNode(const pair<K, V>& kv)
		:_kv(kv)
		, _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
	{ }
};

新节点的默认颜色是RED,如果新插入的节点颜色是BLACK,那么就违反了红黑树的第四条性质,如果新插入的节点默认是RED,则不会出现这样的结果

4、红黑树的插入操作

红黑树的插入可以分为两步:

  1. 按二叉搜索树的规则进行插入
  2. 插入后观察是否符合红黑树的性质

因为新插入的节点默认颜色为RED,所以如果父节点是黑色的,没有违反任何规则,插入结束,但当双亲节点为RED时,就违反了性质三(不存在连续的两个红色节点),此时需要分情况讨论

把新增结点标识为c (cur),c的父亲标识为p(parent),p的父亲标识为

g(grandfather),p的兄弟标识为u(uncle)

4.1 情况1:c为红,p为红,g为黑,u存在且为红

解决方式:将parent和uncle变为黑,grandfather变红,再把grandfather当做新的cur,继续向上调整

4.2 情况2:c为红,p为红,g为黑,u不存在或者u存在且为黑

uncle不存在,则cur一定是新增节点,uncle存在且为黑,,则cur一定不是新增,cur之前是黑色的,是在cur的子树中插入,符合情况1,变色将cur从黑色变成红色,更新上来的

解决方式:p必须变黑,才能解决,连续红色结点的问题,u不存在或者是黑色的,这里单纯的变色无法解决问题,需要旋转+变色

4.2.1 单旋+变色

4.2.2 双旋+变色

4.3 插入完整代码

cpp 复制代码
public:
	bool Insert(const pair<K, V>& kv)
	{

		if (_root == nullptr)
		{
			_root = new Node(kv);
			_root->_col = BLACK;

			return true;
		}

	// ========== 2. 二叉搜索树查找插入位置 ==========
		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;
			}
		}

		// ========== 3. 创建新节点并挂载到父节点 ==========
    // 循环结束cur为空,此处新建节点,cur指向新节点
		cur = new Node(kv);
		cur->_col = RED; //新增节点必须是红色

	// 判断新节点是父节点的左孩子还是右孩子
		if (parent->_kv.first < kv.first)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}
		
		// 双向绑定父子指针:新节点的父指向parent
		cur->_parent = parent;

		// ========== 4. 红黑树平衡修复循环 ==========
    // 进入循环条件:父节点存在 且 父节点是红色
    // 冲突根源:cur红 + parent红 → 连续红色节点,违反红黑树规则
		while (parent && parent->_col == RED)
		{
			Node* grandfather = parent->_parent;
			// 分支一:父节点是祖父的左孩子
			if (grandfather->_left == parent)
			{
				Node* uncle = grandfather->_right;
				// 叔叔存在且为红->变色
				if (uncle && uncle->_col == RED)
				{
					parent->_col = BLACK;  // 父变黑
					uncle->_col = BLACK;  //叔变黑
					grandfather->_col = RED;  // 祖父变红

					// 父当做新的cur,回到循环头部继续向上修复
					cur = grandfather;
					parent = cur->_parent;
				}
				// 场景2/3:叔父不存在 或 叔父黑色,必须旋转修复
				else  // 叔叔不存在,或者叔叔存在且为黑->旋转+变色
				{
					// 场景2:cur是parent的左孩子,同侧结构 → 右单旋
					if (cur == parent->_left)
					{
						//     g
						//  p    u
						//c 
						// 右单旋
						RotateR(grandfather);
						parent->_col = BLACK;  // 旋转后p成为局部子树根,染黑消除连续红
						grandfather->_col = RED; // 原祖父下沉,染红
					}
					
					// 场景3:cur是parent的右孩子,异侧结构 → 左右双旋
					else
					{
						//     g
						//  p     u
						//    c 
						// 左右双旋
						RotateL(parent);
						RotateR(grandfather);
						cur->_col = BLACK;// 旋转后cur成为局部根,染黑
						grandfather->_col = RED;// 祖父下沉染红
					}

					break;
				}
			}
			
			// 分支二:父节点是祖父的右孩子(和左侧逻辑完全对称)
			else // grandfather->_right == parent
			{
				//   g
				// u   p
				Node* uncle = grandfather->_left;
				// 叔叔存在且为红,变色即可
				// 场景1:叔父存在且红色,仅变色
				if (uncle && uncle->_col == RED)
				{
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;

					// 继续往上处理
					cur = grandfather;
					parent = cur->_parent;
				}
				
				// 场景2/3:叔父空/黑色,旋转修复
				else // 叔叔不存在,或者存在且为黑
				{
					// 情况二:叔叔不存在或者存在且为黑
					// 旋转+变色
					//   g
					// u   p
					//       c
					
					// 场景2:cur是parent的右孩子,同侧结构 → 单左旋
					if (cur == parent->_right)
					{
						RotateL(grandfather);
						parent->_col = BLACK; // p变局部根,染黑
						grandfather->_col = RED; // g下沉染红
					}
					
					// 场景3:cur是parent的左孩子,异侧结构 → 右左双旋
					else
					{	//    g
						// u     p
						//     c
						RotateR(parent);
						RotateL(grandfather);
						cur->_col = BLACK;   // cur变局部根,染黑
						grandfather->_col = RED;  // g下沉染红
					}

					break;
				}
			}
		}

		_root->_col = BLACK;
		return true;
	}
	
	private:
	// 右单旋
	void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

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

		Node* parentParent = parent->_parent;

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

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

			subL->_parent = parentParent;
		}
	}

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

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

		Node* parentParent = parent->_parent;

		subR->_left = parent;
		parent->_parent = subR;

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

			subR->_parent = parentParent;
		}
	}

5、红黑树的验证

  1. 枚举颜色类型,天然实现保证了颜色不是黑色就是红色
  2. 前序遍历检查
  3. 前序遍历,遍历过程中用形参记录跟到当前结点的blackNum(黑色结点数量),前序遍历遇到黑色结点就++blackNum,走到空就计算出了一条路径的黑色结点数量。再任意⼀条路径黑色结点数量作为参考值,依次比较即可
cpp 复制代码
bool Check(Node* cur, int blackNum, const int blackNumRef)
{
	if (cur == nullptr)
	{
		if (blackNum != blackNumRef)
		{
			cout << "黑色节点的数量不相等" << endl;
			return false;
		}

		return true;
	}

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

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

  // 用值传递
	return Check(cur->_left, blackNum, blackNumRef)
		&& Check(cur->_right, blackNum, blackNumRef);
}

6、红黑树的其他功能函数

_Height:获取整棵红黑树的高度

Find:根据 key 查找对应节点

_Size:递归统计以root为根的子树节点总数

IsBalance:整体校验红黑树是否合法,满足全部红黑规则

InOrder :对外调用中序遍历,打印树所有 key,内部调用私有递归_InOrder,遍历完成换行。二叉搜索树中序遍历结果是升序有序序列

,用来验证 BST 结构是否正确。

红黑树的总代码:

cpp 复制代码
#pragma once

// 枚举值表示颜色
enum Colour
{
	RED,
	BLACK
};

// 这里我们默认按key/value结构实现
template<class K, class V>
struct RBTreeNode
{
	// 这里更新控制平衡也要加入parent指针
	pair<K, V> _kv;
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _parent;
	Colour _col;
	RBTreeNode(const pair<K, V>& kv)
		:_kv(kv)
		, _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
	{
	}
};

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->_col = RED; //新增节点必须是红色

		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 (grandfather->_left == parent)
			{
				Node* uncle = grandfather->_right;
				// 叔叔存在且为红->变色
				if (uncle && uncle->_col == RED)
				{
					parent->_col = BLACK;
					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 // grandfather->_right == parent
			{
				//   g
				// u   p
				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 InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}

	bool IsBalance()
	{
		if (_root == nullptr)
			return true;

		if (_root->_col == RED)
			return false;

		// 黑色节点数量参考值
		Node* leftMost = _root;
		int blackRef = 0;
		while (leftMost)
		{
			if (leftMost->_col == BLACK)
				++blackRef;

			leftMost = leftMost->_left;
		}

		return Check(_root, 0, blackRef);
	}

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

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

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

private:
	int _Size(Node* root)
	{
		return root == nullptr ? 0 : _Size(root->_left) + _Size(root->_right) + 1;
	}

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

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

			return true;
		}

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

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

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

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

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

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

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

		Node* parentParent = parent->_parent;

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

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

			subL->_parent = parentParent;
		}
	}

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

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

		Node* parentParent = parent->_parent;

		subR->_left = parent;
		parent->_parent = subR;

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

			subR->_parent = parentParent;
		}
	}
private:
	Node* _root = nullptr;
};

7、红黑树与AVL树的比较

AVL树 对于平衡的要求会更高,AVL树查询节点的时间复杂度为 logN,红黑树的最长路径可以为最短路径的2倍,因此查询节点的时间复杂度为 log 2*N,红黑树实现比较简单,所以实际运用中红黑树更多,更广泛

相关推荐
aaPIXa6224 小时前
C++采样引导优化SPGO——比PGO更智能的编译器决策新方案
java·c++·人工智能
Starmoon_dhw4 小时前
题解:P17078 夏日甜点
c++·学习·算法·图论
某不知名網友4 小时前
C/C++动态内存管理,智能指针及RAlI思想。
java·c语言·c++
hold?fish:palm5 小时前
从源码到可执行文件:C++程序的编译过程
开发语言·c++
lingran__5 小时前
C++_STL简介
开发语言·c++
南国韭菜6 小时前
【无标题】
c++·qt
没用的阿_吉6 小时前
vs2022offline 生成
c++
qq_416409137 小时前
专业的openclaw推荐
c++
一个小猴子`8 小时前
CUDA全局内存
c++·cuda编程