当前位置: 首页 > news >正文

红黑树——如何靠控制色彩实现平衡的?

目录

引言

一、认识红黑树(RBTree)

二、为什么有了AVL树,还要红黑树?

1、AVL树 vs 红黑树,两棵树区别

2、如何选择?

三、红黑树的核心操作

3.1、红黑树结构定义

3.2、插入操作

四、红黑树的验证

五、完整代码


引言

什么是红黑树呢?

红黑树和AVL树一样也是一种平衡搜索树,只不过红黑树控制平衡的方式不一样,是一种近似平衡。 下面会详解他是如何维持平衡的。


一、认识红黑树(RBTree)

红黑树在节点的的定义上新加了个颜色属性,分别是RedBlack,这也是它为什么叫红黑树的原因了。通过节点之间颜色等的限制,来保证任意节点路径的节点个数不超过最短路径的两倍。所以它是近似平衡的。

保证平衡的核心性质:

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

红黑树示例图:

为什么满足以上性质,就能保证红黑树的最长路径不超过最短路径的两倍呢?

注意性质3、4的条件下:

最短路径:一定是连续的黑色节点

最长路径:一定是红黑节点交替出现。类似下面这种样子:

所以在不破坏红黑树性质的情况下,红黑树的最长路径一定不超过最短路径的两倍。


二、为什么有了AVL树,还要红黑树?

1、AVL树 vs 红黑树,两棵树区别

AVL树特点:

  1. 是一颗严格平衡的二叉搜索树(左右子树高度差不超过1)
  2. 但是为了维护平衡因子,插入/删除可能需要多次旋转,效率O(logN)
  3. 查找效率嘎嘎快

红黑树特点:

  1. 是一颗近似平衡的二叉搜素树(最长路径 <= 2*最短路径)
  2. 树不平衡时,可以通过变色O(1)+旋转解决,插入/删除的旋转次数少
  3. 查找效率比AVL树慢点

插入效率分析:
假设向AVL树和红黑树分别插入1000个值,两棵树的高度分别是多少呢?

AVL:h \approx log(1000) \approx 10

红黑树:因为最长路径小于最短路径的2倍,所以 h \leq 2*log(1000) \approx 20

假设向AVL树和红黑树分别插入10亿个值,两棵树的高度分别是多少呢?

AVL:h \approx log(1000000000) \approx 30

红黑树:h \leq 2*log(1000000000) \approx 60

综上所述:

对于CPU而言跑10次/20次、30次/60次差别不大,说明这两颗树的性能是同一量级的。但是AVL树为了严格控制平衡是付出代价的,插入和删除需要大量的旋转。所以,红黑树也是很优秀的,也比AVL树好控制点。C++里的map/set也是用红黑树设计的。

2、如何选择?

  • 若查询次数 > 插入/删除次数(如字典)———  AVL树
  • 若插入/删除频繁(如游戏中的实时排行榜)——— 红黑树
  • 不确定时:默认选红黑树,它在综合场景下表现更稳健。

三、红黑树的核心操作

3.1、红黑树结构定义

enum Color
{RED,BLACK
};template<class K,class V>
struct RBTreeNode
{pair<K, V> _kv; // 节点的值,存储键值对RBTreeNode<K, V>* _parent; // 指向父节点RBTreeNode<K, V>* _left; // 指向左孩子RBTreeNode<K, V>* _right; // 指向右孩子Color _col; // 节点的颜色RBTreeNode(const pair<K, V>& kv):_kv(kv),_parent(nullptr),_left(nullptr),_right(nullptr),_col(RED){}
};//红黑树实现
template<class K,class V>
class RBTree
{typedef RBTreeNode<K, V> Node;
public:// ......private:Node* _root = nullptr;
};

3.2、插入操作

先分析一下,当我们要插入一个节点时,节点的颜色设置成红色还是黑色?

答案红色。在考虑性质3和性质4时,插入的颜色是红色的话,不会影响其他路径,只会影响当前路径。但如果插入节点颜色是黑色的话,性质4被破坏了,并且会影响其它路径。

插入情况分析:
由上图可知:只有当我们插入节点颜色为红色且父节点也为红色时,整棵树才违反红黑树规则需要调整来维持平衡。这里直接分析具体情况,其他情况类似分析即可:

情况1:cur为红,parent为红,grandfather为黑,uncle存在且为红

情况2: cur为红,parent为红,grandfather为黑,uncle不存在

在向上面那样处理的话,违反每条路径上黑色节点的数量相等

情况3: cur为红,parent为红,grandfather为黑,uncle存在且为黑

总结:

红黑树插入关键看uncle节点

  1. uncle存在且为红,变色+向上更新
  2. uncle不存在/uncle存在且为黑,旋转+变色

具体是左单旋,右单旋还是双旋看情况分析

代码示例:
 

bool insert(const pair<K, V>& kv)
{//这里按二叉搜索树规则找合适位置插入就行,主要看平衡操作// ......//控制红黑树平衡while (parent && parent->_col == RED){Node* grandfather = parent->_parent;if (parent == grandfather->_left){Node* uncle = grandfather->_right;if (uncle && uncle->_col == RED){//变色grandfather->_col = RED;parent->_col = uncle->_col = BLACK;//继续向上处理cur = grandfather;parent = cur->_parent;}else //uncle不存在 或 uncle == BLACK{if (parent->_left == cur){//      g//   p//cRotateR(grandfather);//变色parent->_col = BLACK;grandfather->_col = RED;}else {//      g//   p//      cRotateL(parent);RotateR(grandfather);//变色cur->_col = BLACK;grandfather->_col = RED;}//break;}}else // grandfather->right == parent{Node* uncle = grandfather->_left;if (uncle && uncle->_col == RED){//变色grandfather->_col = RED;parent->_col = uncle->_col = BLACK;//继续向上处理cur = grandfather;parent = cur->_parent;}else //uncle为空 或 uncle == BLACK{if (cur == parent->_right){// g//   p//     cRotateL(grandfather);//变色grandfather->_col = RED;parent->_col = BLACK;}else // cur == parent->left{// g//   p// cRotateR(parent);RotateL(grandfather);//变色grandfather->_col = RED;cur->_col = BLACK; }//break;}}}_root->_col = BLACK;return true;
}

四、红黑树的验证

红黑树的检测分为两步:

1. 检测其是否满足二叉搜索树(中序遍历是否为有序序列)

2. 检测其是否满足红黑树的性质

// 检查红黑树性质的核心递归函数
// root: 当前检查的节点
// blacknum: 当前路径已累积的黑色节点数(按值传递保持路径独立)
// benchmark: 基准黑高(从根到最左叶子的黑色节点数)
bool CheckColor(Node* root, int blacknum, int benchmark) 
{// 递归终止条件:到达叶子节点(NIL)if (root == nullptr) {// 检查当前路径黑高是否等于基准值if (benchmark != blacknum) {return false; // 黑高不一致,违反性质5}return true; // 当前路径合法}// 性质2:根节点为黑已在入口函数检查,此处无需处理// 统计黑色节点数量if (root->_col == BLACK) {++blacknum; // 遇到黑色节点,累计计数}// 性质4:检查连续红色节点if (root->_col == RED && root->_parent && root->_parent->_col == RED) {cout << root->_kv.first << "出现连续红色节点" << endl; // 输出违规位置return false; // 父子节点均为红,违反性质4}// 递归检查左右子树(注意blacknum是值传递,左右子树计数独立)return CheckColor(root->_left, blacknum, benchmark) && CheckColor(root->_right, blacknum, benchmark);
}// 外部调用的平衡性检查接口
bool IsBalance() 
{return IsBalance(_root); // 从根节点开始检查
}// 内部实现的平衡性检查入口
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; // 沿左子树深入}// 从根节点开始递归检查所有路径,初始黑高计数为0return CheckColor(root, 0, benchmark);
}

五、完整代码

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->_left;}else if (cur->_kv.first < kv.first){parent = cur;cur = cur->_right;}else{return false;}}cur = new Node(kv);cur->_col = RED;if (parent->_kv.first > kv.first){parent->_left = cur;}else{parent->_right = 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){//变色grandfather->_col = RED;parent->_col = uncle->_col = BLACK;//继续向上处理cur = grandfather;parent = cur->_parent;}else //uncle不存在 或 uncle == BLACK{if (parent->_left == cur){//      g//   p//cRotateR(grandfather);//变色parent->_col = BLACK;grandfather->_col = RED;}else {//      g//   p//      cRotateL(parent);RotateR(grandfather);//变色cur->_col = BLACK;grandfather->_col = RED;}//break;}}else // grandfather->right == parent{Node* uncle = grandfather->_left;if (uncle && uncle->_col == RED){//变色grandfather->_col = RED;parent->_col = uncle->_col = BLACK;//继续向上处理cur = grandfather;parent = cur->_parent;}else //uncle为空 或 uncle == BLACK{if (cur == parent->_right){// g//   p//     cRotateL(grandfather);//变色grandfather->_col = RED;parent->_col = BLACK;}else // cur == parent->left{// g//   p// cRotateR(parent);RotateL(grandfather);//变色grandfather->_col = RED;cur->_col = BLACK; }//break;}}}_root->_col = BLACK;return true;}void RotateR(Node* parent){Node* cur = parent->_left;Node* curright = cur->_right;parent->_left = curright;if (curright){curright->_parent = parent;}Node* ppnode = parent->_parent;parent->_parent = cur;cur->_right = parent;if (_root == parent){_root = cur;cur->_parent = nullptr;}else{if (ppnode->_left == parent){ppnode->_left = cur;cur->_parent = ppnode;}else{ppnode->_right = cur;cur->_parent = ppnode;}}}void RotateL(Node* parent){Node* cur = parent->_right;Node* curleft = cur->_left;parent->_right = curleft;if (curleft){curleft->_parent = parent;}Node* ppnode = parent->_parent;cur->_left = parent;parent->_parent = cur;if (_root == parent){_root = cur;cur->_parent = nullptr;}else{if (ppnode->_left == parent){ppnode->_left = cur;cur->_parent = ppnode;}else{ppnode->_right = cur;cur->_parent = ppnode;}}}private:Node* _root = nullptr;
};

相关文章:

  • 记录搭建自己应用中心
  • OpenAI 推出「轻量级」Deep Research,免费用户同享
  • CSS 入门全解析
  • 0. Selenium工具的安装
  • deep鼠标跟随插件
  • 跟着尚硅谷学vue-day5
  • NVIDIA 高级辅助驾驶汽车安全系统 系列读后感(1)
  • 数据湖DataLake和传统数据仓库Datawarehouse的主要区别是什么?优缺点是什么?
  • excel如何绘制直方图
  • 赛灵思 XCKU115-2FLVB2104I Xilinx Kintex UltraScale FPGA
  • Kafka Tool(Offset Explorer)国内下载: Kafka可视化连接工具
  • mysql基础——数据表查询(全面解析)
  • Docker拉取镜像代理配置实践与经验分享
  • 删除新安装IBM Guardium Data Protection 12.1的baltimorecybertrustroot证书
  • 基于ssm的手机商城管理系统(源码+数据库)
  • 复合材料高置信度 DIC 测量与高级实验技术研讨会邀请函
  • spark-standalone模式
  • @Conditional
  • 【Linux庖丁解牛】—进程优先级!
  • 小白如何使用Cursor运行python程序(含环境配置教程)
  • 夜读丨怀念那个写信的年代
  • 央视曝光假进口保健品:警惕!保税仓发货不等于真进口
  • 加拿大温哥华一车辆冲撞人群,造成多人伤亡
  • 持续更新丨伊朗港口爆炸事件已致406人受伤
  • 本周看啥|在电影院里听民谣,听摇滚,燥起来吧
  • 贵州赤水被指“整改复耕”存形式主义,当地部署耕地流出整改“回头看”