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

C++ 红黑树

上一节我介绍了二叉搜索树家族的AVL树,这里我们来介绍二叉搜索树家族的另一个成员,也是使用最广泛的成员。

1.AVL树与红黑树的区别

  • 平衡性质
    • AVL 树:是严格的平衡二叉树,要求任意节点的左右子树高度差的绝对值不超过 1,能保证树的高度始终保持在O(logn)级别,查询效率非常稳定。
    • 红黑树:是一种弱平衡的二叉树,通过节点颜色和一些规则来保证从根节点到叶子节点的最长路径不超过最短路径的 2 倍,虽然不能像 AVL 树那样严格控制高度,但在实际应用中也能提供较为高效的操作。
  • 插入和删除操作
    • AVL 树:插入和删除节点后,为了保持平衡,可能需要进行多次旋转操作,调整的频率相对较高,导致其插入和删除操作的时间复杂度相对不稳定。
    • 红黑树:插入和删除操作相对简单,因为它的平衡条件相对宽松,所以在插入和删除时需要进行的调整操作通常比 AVL 树少,这使得红黑树在频繁进行插入和删除操作的场景下,效率可能更高。
  • 空间复杂度
    • AVL 树:节点结构相对简单,通常只需要存储数据和左右子节点的指针,不需要额外的空间来存储颜色等信息,所以空间复杂度相对较低。
    • 红黑树:由于需要为每个节点设置颜色属性,因此每个节点需要额外的存储空间来存储颜色信息,这使得红黑树的空间复杂度相对 AVL 树略高。
  • 应用场景
    • AVL 树:适用于查询操作非常频繁,对查询效率要求极高,且数据相对稳定,插入和删除操作较少的场景,如数据库索引、编译器的符号表等。
    • 红黑树:在插入和删除操作比较频繁,同时对查询效率也有一定要求的场景中表现出色,如 STL 中的 map、set 等容器,以及 Linux 内核中的进程调度等。

2.红黑树的概念

红⿊树是⼀棵⼆叉搜索树,他的每个结点增加⼀个存储位来表⽰结点的颜⾊,可以是红⾊或者⿊⾊。 通过对任何⼀条从根到叶⼦的路径上各个结点的颜⾊进⾏约束,红⿊树确保没有⼀条路径会⽐其他路径⻓出2倍,因⽽是接近平衡的。

2.1 红黑树的规则

为了完成红黑树的功能,设计者设计了以下规则来控制

1. 每个结点不是红⾊就是⿊⾊
2. 根结点是⿊⾊的
3. 如果⼀个结点是红⾊的,则它的两个孩⼦结点必须是⿊⾊的,也就是说任意⼀条路径不会有连续的
红⾊结点。
4. 对于任意⼀个结点,从该结点到其所有NULL结点的简单路径上,均包含相同数量的⿊⾊结点

由规则4可知,从根到NULL结点的每条路径都有相同数量的⿊⾊结点,所以极端场景下,最短路径 就就是全是⿊⾊结点的路径,假设最短路径⻓度为bh(black height)。
由规则2和规则3可知,任意⼀条路径不会有连续的红⾊结点,所以极端场景下,最⻓的路径就是⼀⿊⼀红间隔组成,那么最⻓路径的⻓度为2*bh。
•  综合红⿊树的4点规则⽽⾔,理论上的全⿊最短路径和⼀⿊⼀红的最⻓路径并不是在每棵红⿊树都存在的。假设任意⼀条从根到NULL结点路径的⻓度为x,那么bh <= h <= 2*bh。

2.2红黑树的效率

假设N是红⿊树树中结点数量,h最短路径的⻓度,那么2 ^h − 1 <= N < 2^( 2∗h) − 1 , 由此推出h logN  ,也就是意味着红⿊树增删查改最坏也就是⾛最⻓路径2 ∗ logN ,那么时间复杂度还是  O( logN )。
红⿊树的表达相对AVL树要抽象⼀些,AVL树通过⾼度差直观的控制了平衡。红⿊树通过4条规则的颜 ⾊约束,间接的实现了近似平衡,他们效率都是同⼀档次,但是相对⽽⾔,插⼊相同数量的结点,红⿊树的旋转次数是更少的,因为他对平衡的控制没那么严格。

3.红黑树的实现

3.1红黑树的结构

enum Colour
{RED,BLACK
};
template<class K,class V>
struct RBTreeNode
{pair<K, V> _kv;RBTreeNode* _left;RBTreeNode* _right;RBTreeNode* _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:
private:Node* _root = nullptr;
};

3.2 红黑树的插入

3.2.1红⿊树树插⼊⼀个值的⼤概过程

1. 插⼊⼀个值按⼆叉搜索树规则进⾏插⼊,插⼊后我们只需要观察是否符合红⿊树的4条规则。
2. 如果是空树插⼊,新增结点是⿊⾊结点。如果是⾮空树插⼊,新增结点必须红⾊结点,因为⾮空树插⼊,新增⿊⾊结点就破坏了规则4,规则4是很难维护的。
3. ⾮空树插⼊后,新增结点必须红⾊结点,如果⽗亲结点是⿊⾊的,则没有违反任何规则,插⼊结束。
4. ⾮空树插⼊后,新增结点必须红⾊结点,如果⽗亲结点是红⾊的,则违反规则3。进⼀步分析,c是 红⾊,p为红,g必为⿊,这三个颜⾊都固定了,关键的变化看u的情况,需要根据u分为以下⼏种情况分别处理。

3.2.2情况1:变⾊

c为红,p为红,g为⿊,u存在且为红,则将p和u变⿊,g变红。在把g当做新的c,继续往上更新。
分析:因为p和u都是红⾊,g是⿊⾊,把p和u变⿊,左边⼦树路径各增加⼀个⿊⾊结点,g再变红,相 当于保持g所在⼦树的⿊⾊结点的数量不变,同时解决了c和p连续红⾊结点的问题,需要继续往上更新 是因为,g是红⾊,如果g的⽗亲还是红⾊,那么就还需要继续处理;如果g的⽗亲是⿊⾊,则处理结束了;如果g就是整棵树的根,再把g变回⿊⾊。
情况1只变⾊,不旋转。所以⽆论c是p的左还是右,p是g的左还是右,都是上⾯的变⾊处理⽅式。

3.2.3情况2:单旋+变⾊

c为红,p为红,g为⿊,u不存在或者u存在且为⿊,u不存在,则c⼀定是新增结点,u存在且为⿊,则c⼀定不是新增,c之前是⿊⾊的,是在c的⼦树中插⼊,符合情况1,变⾊将c从⿊⾊变成红⾊,更新上来的。 分析:p必须变⿊,才能解决,连续红⾊结点的问题,u不存在或者是⿊⾊的,这⾥单纯的变⾊⽆法解决问题,需要旋转+变⾊。

3.2.4情况2:双旋+变⾊

c为红,p为红,g为⿊,u不存在或者u存在且为⿊,u不存在,则c⼀定是新增结点,u存在且为⿊,则 c⼀定不是新增,c之前是⿊⾊的,是在c的⼦树中插⼊,符合情况1,变⾊将c从⿊⾊变成红⾊,更新上来的。 分析:p必须变⿊,才能解决,连续红⾊结点的问题,u不存在或者是⿊⾊的,这⾥单纯的变⾊⽆法解决问题,需要旋转+变⾊。

3.3红⿊树的插⼊代码实现

#pragma onceenum Colour
{RED,BLACK
};
template<class K,class V>
struct RBTreeNode
{pair<K, V> _kv;RBTreeNode* _left;RBTreeNode* _right;RBTreeNode* _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){parent = cur;if (cur->_kv.first > kv.first){cur = cur->_left;}else if(cur->_kv.first<kv.first){cur = cur->_right;}else{return false;}}cur = new Node(kv);cur->_parent = parent;cur->_col = RED;if (kv.first < parent->_kv.first){parent->_left = cur;}else{parent->_right = cur;}//控制颜色的平衡while (parent && parent->_col == RED){Node* grandparent = parent->_parent;Node* uncle;if (parent == grandparent->_left){uncle = grandparent->_right;}else{uncle = grandparent->_left;}//uncle存在且为红色if(uncle && uncle->_col == RED){grandparent->_col = RED;uncle->_col = BLACK;parent->_col = BLACK;cur = grandparent;}else {if (parent == grandparent->_left){if(cur == parent->_left){RotateR(grandparent);grandparent->_col = RED;parent->_col = BLACK;}else{RotateLR(grandparent);cur->_col = BLACK;grandparent->_col = RED;}break;}else{if (cur == parent->_right){RotateL(grandparent);grandparent->_col = RED;parent->_col = BLACK;}else{RotateRL(grandparent);cur->_col = BLACK;grandparent->_col = RED;}break;}}}_root->_col = BLACK;return true;}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;}void InOrder(){_InOrder(_root);}void _InOrder(const Node* root){if (root == nullptr)return;_InOrder(root->_left);cout << root->_kv.first << ' ';_InOrder(root->_right);}void RotateRL(Node* parent){Node* parentR = parent->_right;Node* parentRL = parentR->_left;RotateR(parent->_right);RotateL(parent);}void RotateLR(Node* parent){Node* parentL = parent->_left;Node* parentLR = parentL->_right;RotateL(parent->_left);RotateR(parent);}void RotateR(Node* parent){Node* parentL = parent->_left;Node* parentLR = parentL->_right;Node* parentPa = parent->_parent;if (parentPa){if (parent == parentPa->_left)parentPa->_left = parentL;elseparentPa->_right = parentL;}if (parentLR)parentLR->_parent = parent;parent->_parent = parentL;parent->_left = parentLR;parentL->_right = parent;parentL->_parent = parentPa;}void RotateL(Node* parent){Node* parentR = parent->_right;Node* parentRL = parentR->_left;Node* parentPa = parent->_parent;if (parentPa){if (parent == parentPa->_left)parentPa->_left = parentR;elseparentPa->_right = parentR;}if (parentRL)parentRL->_parent = parent;parent->_parent = parentR;parent->_right = parentRL;parentR->_parent = parentPa;parentR->_left = parent;}private:Node* _root = nullptr;
};

 3.4红⿊树的查找

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

3.5红⿊树的验证

这⾥获取最⻓路径和最短路径,检查最⻓路径不超过最短路径的2倍是不可⾏的,因为就算满⾜这个条件,红⿊树也可能颜⾊不满⾜规则,当前暂时没出问题,后续继续插⼊还是会出问题的。所以我们还是去检查4点规则,满⾜这4点规则,⼀定能保证最⻓路径不超过最短路径的2倍。
1. 规则1枚举颜⾊类型,天然实现保证了颜⾊不是⿊⾊就是红⾊。
2. 规则2直接检查根即可
3. 规则3前序遍历检查,遇到红⾊结点查孩⼦不太⽅便,因为孩⼦有两个,且不⼀定存在,反过来检查⽗亲的颜⾊就⽅便多了。
4. 规则4前序遍历,遍历过程中⽤形参记录跟到当前结点的blackNum(⿊⾊结点数量),前序遍历遇到⿊⾊结点就++blackNum,⾛到空就计算出了⼀条路径的⿊⾊结点数量。再任意⼀条路径⿊⾊结点数量作为参考值,依次⽐较即可。

bool IsRBTree()
{return _IsRBTree(_root);
}
bool _IsRBTree(const Node* root)
{if (root == nullptr)return true;if (root->_col == RED)return false;Node* cur = root;int RetNum = 0;while (cur){if (cur->_col == BLACK)RetNum++;cur = cur->_left;}return Check(root, 0, RetNum);
}bool Check(const Node*& root,int BlackNum,int refNum)
{if (root == nullptr){if (BlackNum != refNum){cout << "黑色节点数不匹配" << endl;return false;}return true;}if (root->_col == RED && root->_parent->_col == BLACK){cout << "红色节点重复" << endl;return false;}if (root->_col == BLACK){BlackNum++;}return Check(root->_left, BlackNum, refNum) && Check(root->_right, BlackNum, refNum);
}

http://www.xdnf.cn/news/214219.html

相关文章:

  • 第14讲:科研图表的导出与排版艺术——高质量 PDF、TIFF 输出与投稿规范全攻略!
  • Java 基础--运算符全解析
  • Ubuntu搭建 Nginx以及Keepalived 实现 主备
  • ‘WebDriver‘ object has no attribute ‘find_element_by_class‘
  • 咖啡的功效与作用及副作用,咖啡对身体有哪些好处和坏处
  • 什么是缓冲区溢出?NGINX是如何防止缓冲区溢出攻击的?
  • [逆向工程]什么是CPU寄存器(三)
  • Qt开发之C++泛型编程进阶
  • C语言教程(二十五):C 语言函数可变参数详解
  • 机器学习-入门-决策树(1)
  • 大模型微调之LLaMA-Factory 系列教程大纲
  • 面试篇 - LoRA(Low-Rank Adaptation) 原理
  • java每日精进 4.29【框架之自动记录日志并插入如数据库流程分析】
  • C++ 单例对象自动释放(保姆级讲解)
  • 马井堂-区块链技术:架构创新、产业变革与治理挑战(马井堂)
  • python用切片的方式取元素
  • 基于GPT 模板开发智能写作辅助应用
  • 1.PowerBi保姆级安装教程
  • HarmonyOS运动开发:如何监听用户运动步数数据
  • 怎么查自己手机连接的ip归属地:完整指南
  • E2E 测试
  • 在 JMeter 中使用 BeanShell 获取 HTTP 请求体中的 JSON 数据
  • 某建筑石料用灰岩矿自动化监测
  • dify升级最新版本(保留已创建内容)
  • React 第三十五节 Router 中useNavigate 的作用及用途详解
  • 【Java学习】动态代理有哪些形式?
  • Windows服务管理
  • Electron-vite中ELECTRON_RENDERER_URL环境变量如何被设置的
  • 偶然发现Git文件夹非常大,使用BGF来处理Git历史Blob文件
  • Python类的力量:第一篇:数据组织革命——用类替代“临时数据结构”