首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >【C++篇】平衡二叉搜索树(下篇):红黑树详解

【C++篇】平衡二叉搜索树(下篇):红黑树详解

作者头像
我想吃余
发布2025-08-21 08:43:14
发布2025-08-21 08:43:14
13800
代码可运行
举报
文章被收录于专栏:C语言学习C语言学习
运行总次数:0
代码可运行

一、红黑树的概念

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

红黑树规则

  1. 每个节点不是红色就是黑色
  2. 根节点一定是黑色的
  3. 任何路径,没有连续的红色节点
  4. 每条路径上的黑色节点数量相同
  5. 每个NIL节点(叶子节点左右的两个空节点)都是黑色的

二、红黑树类和节点的定义

代码语言:javascript
代码运行次数:0
运行
复制
// 颜色枚举,用于红黑树节点颜色标记
enum Color
{
    BLACK,  // 黑色
    RED     // 红色
};

// 红黑树节点模板类
template<class K, class V>
struct BRTreeNode
{
    BRTreeNode<K, V>* _left;    // 左子节点指针
    BRTreeNode<K, V>* _right;   // 右子节点指针
    BRTreeNode<K, V>* _parent;  // 父节点指针
    pair<K, V> _kv;             // 存储的键值对
    Color _col;                 // 节点颜色(红或黑)

    // 构造函数
    BRTreeNode(const pair<K, V>& kv)
        :_left(nullptr)     // 初始化左子节点为空
        ,_right(nullptr)    // 初始化右子节点为空
        ,_parent(nullptr)   // 初始化父节点为空
        ,_kv(kv)           // 初始化键值对
        ,_col(RED)         // 新节点默认为红色(根据红黑树规则)
    { }
};

// 红黑树模板类
template<class K, class V>
class BRTree
{
    // 类型重定义,简化代码
    typedef BRTreeNode<K, V> Node;
public:
    // 公共接口
    // 通常包括插入、删除、查找等操作
    //…………
    
private:
    Node* _root = nullptr;  // 红黑树的根节点,初始化为空
};

三、红黑树的插入操作

因为红黑树也是一个二叉搜索树,所以插入大体步骤和AVL树相同:

  1. 查找插入的位置
  2. 插入
  3. 若插入破坏平衡,调节至平衡,结束。

步骤1和步骤2,不过多赘述了: 代码:

代码语言:javascript
代码运行次数:0
运行
复制
// 在红黑树中插入值为kv的节点
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->_left = cur;

	}
	else//新增在右
	{
		parent->_right = cur;
	}
	cur->_parent = parent;

	//若插入破坏平衡,调节至平衡
	//…………
	return true;
}

在AVL树中,维持平衡主要依靠的是更新平衡因子和旋转。 而在红黑树中,也是类似的:更新颜色旋转

现在,我们需要考虑一个问题:我们插入的节点设置为黑色还是红色?

假设我们设置为黑色,那么无论插入在哪条路径,红黑树的平衡一定会被破坏(每条路径上的黑色节点数量相同)。

设置为红色,如果插入在黑色节点后,红黑树依然保持平衡,但插入在红色节点后,会产生连续的红色节点,红黑树的平衡被破坏(任何路径,没有连续的红色节点)

对比之后,插入的节点颜色设置为红色更合适

那么,如何平衡呢?

控制平衡的关键节点在于:新增节点的uncle(叔叔)节点

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

分为三种情况:

  • uncle存在,且为红色节点

平衡方法

  1. p和u都设置为黑
  2. g设置为红
  3. 沿着祖先路径往上更新
    • g有父节点,且父节点为黑,平衡结束
    • g有父节点,且父节点为红,继续往上更新
    • g无父节点,说明g为根节点,g变黑即可,平衡结束

图解示例:

  • uncle不存在

平衡方法

  1. 先旋转
  2. 后变色
    • 若为单旋:p变为黑色;g变为红色
    • 若为双旋:c变为黑色;g变为红色

单旋图解示例:

  • uncle存在,且为黑色

这个情况的解决方法与前文uncle不存在相同。

双旋图解示例:

平衡红黑树代码

代码语言:javascript
代码运行次数:0
运行
复制
//更新颜色
//检查是否需要更新颜色,若parent为黑则无需更新
while (parent && parent->_col == RED)
{
	Node* grandfather = parent->_parent;
	Node* uncle = nullptr;
	if (grandfather->_left == parent)
	{
		uncle = grandfather->_right;
	}
	else
	{
		uncle = grandfather->_left;
	}

	if (uncle && uncle->_col == RED)//uncle存在且为红——变色
	{
		parent->_col = uncle->_col = BLACK;
		grandfather->_col = RED;

		if (grandfather->_parent)
		{
			if (grandfather->_col == BLACK)
			{
				break;
			}
			else
			{
				cur = grandfather;
				parent = cur->_parent;
			}
		}
		else
		{
			grandfather->_col = BLACK;
			break;
		}
	}
	else//uncle不存在或uncle存在且为黑——旋转+变色
	{
		//判断何种情况,该用何种旋转
		if (grandfather->_right == parent)
		{
			if (cur == parent->_right)
			{
				//  g
				//     p
				//		  c
				RotateL(grandfather);
				parent->_col = BLACK;
				grandfather->_col = RED;
			}
			else
			{
				//  g
				//     p
				//  c
				RotateR(parent);
				RotateL(grandfather);
				cur->_col = BLACK;
				grandfather->_col = RED;
			}
		}
		else
		{
			if (cur == parent->_left)
			{
				//        g
				//     p
				//  c
				RotateR(grandfather);
				parent->_col = BLACK;
				grandfather->_col = RED;
			}
			else
			{
				//    g
				//  p
				//    c
				RotateL(parent);
				RotateR(grandfather);
				cur->_col = BLACK;
				grandfather->_col = RED;
			}
		}
		break;
	}
}
_root->_col = BLACK;

四、红黑树的验证

红黑树的检测分为两步:

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

代码:

代码语言:javascript
代码运行次数:0
运行
复制
bool CheckCol(Node* root, int BNum, int benchmark)
{
	//走到null之后,判断k和black是否相等
	if (root == nullptr)
	{
		if (BNum != benchmark)
			return false;
		return true;
	}
	
	// 统计黑色节点的个数
	if (root->_col == BLACK)
		++BNum;
		
	//检查但前节点与父节点是否红色节点连续
	if (root->_col == RED && root->_parent && root->_parent->_col == RED)
	{
		cout << root->_kv.first << "出现连续红色节点" << endl;
		return false;
	}

	return CheckCol(root->_left, BNum, benchmark)
		&& CheckCol(root->_right, BNum, benchmark);
}

bool IsBalance(Node* root)
{
	// 空树也是红黑树
	if (root == nullptr)
	{
		return true;
	}
	// 检测根节点是否满足情况
	if (root->_col != BLACK)
	{
		return false;
	}

	//获取任意一条路径中黑色节点的个数作为基准值
	int benchmark = 0;
	Node* cur = root;
	while (cur)
	{
		if (cur->_col == BLACK)
			benchmark++;
		cur = cur->_left;
	}
	// 检测是否满足红黑树的性质,k用来记录路径中黑色节点的个数
	size_t k = 0;
	return CheckCol(root, k, benchmark);
}

五、红黑树与AVL树

红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是

O(log N)

,红黑树不追求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数,所以在经常进行增删的结构中性能比AVL树更优,而且红黑树实现比较简单,所以实际运用中红黑树更多。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-08-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、红黑树的概念
  • 二、红黑树类和节点的定义
  • 三、红黑树的插入操作
  • 四、红黑树的验证
  • 五、红黑树与AVL树
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档