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

【C++】深入理解红黑树:概念、性质和实现

一、红黑树的概念

红黑树是一种二叉搜索树,但它在每个节点上增加了一个存储位来表示节点的颜色,颜色只能是红色(Red)或黑色(Black)。通过对任何一条从根到叶子的路径上各个节点着色方式的限制,红黑树确保没有一条路径会比其他路径长出两倍(最长路径不超过最短路径的2倍),因而近似于平衡的二叉搜索树。


二、红黑树的性质

  • 每个结点不是红色就是黑色
  • 根节点是黑色的
  • 如果一个节点是红色的,则它的两个孩子结点是黑色的(任何路径没有连续的红色节点)
  • 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均 包含相同数目的黑色结点(每条路径上黑色节点的数量相等)
  • 每个叶子结点都是黑色的(此处的叶子结点指的是空结点,即NIL节点)

基于以上性质,红黑树能够保证:其最长路径中节点个数不会超过最短路径节点个数的两倍。


三、红黑树的实现

(一)红黑树节点的定义

首先定义红黑树节点的结构,包含左右子节点指针、父节点指针、数据以及颜色信息:

enum Color
{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;Color _col;RBTreeNode(const pair<K, V>& kv):_left(nullptr), _right(nullptr), _parent(nullptr), _kv(kv), _col(RED){}
};

(二)红黑树的插入

红黑树的插入是在二叉搜索树插入的基础上,通过一系列调整(变色、旋转)来维持红黑树的性质。下面结合图示和原理,详细拆解插入过程。

1、插入的前置知识

红黑树的新节点默认颜色为红色。这是因为:若新节点为黑色,会直接破坏 “每条路径黑色节点数相同” 的性质,需要大量调整;而红色节点若父节点为黑色,不会破坏任何性质,只有父节点也为红色时,才需要调整(避免连续红节点)。

2、插入的核心逻辑

红黑树是在二叉搜索树的基础上加上其平衡限制条件 ,因此插入分为两步:

        a.  按二叉搜索树规则插入新节点:找到合适位置,插入红色新节点。

        b.  调整红黑树性质:若插入后出现 “连续红节点”(父节点也为红色),则根据叔叔节点(uncle)的颜色,分情况调整。

3、分情况调整(关键看uncle的颜色)

我们先来看看可能的插入情况:

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

约定:cur为新插入节点,p为父节点,g为祖父节点,u为叔叔节点(p的兄弟节点)。

  • 情况一:cur为红,p为红,g为黑,u存在且为红

调整方法:

        a.  变色:pu变为黑色,g变为红色

        b.  继续向上检查:g视为新的cur,重复调整逻辑(因为g变红后,若其父亲也为红色,会再次出现连续红节点)。

  • 情况二:cur为红,p为红,g为黑,u不存在/u存在且为黑

  • 情况三:cur为红,p为红,g为黑,u不存在/u存在且为黑

4、最终收尾:根节点必须为黑色

无论上述哪种调整,最后都要检查根节点:若根节点因调整变为红色,需将其改回黑色(保证 “根节点为黑色” 的性质)。

5、插入操作的完整代码
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 (parent == grandfather->_left){Node* uncle = grandfather->_right;// u存在且为红if (uncle && uncle->_col == RED){// 变色parent->_col = uncle->_col = BLACK;grandfather->_col = RED;// 继续向上处理cur = grandfather;parent = cur->_parent;}else // u不存在 或 存在且为黑{if (cur == parent->_left){//     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 // parent == grandfather->_right{Node* uncle = grandfather->_left;// u存在且为红if (uncle && uncle->_col == RED){// 变色parent->_col = uncle->_col = BLACK;grandfather->_col = RED;// 继续向上处理cur = grandfather;parent = cur->_parent;}else{if (cur == parent->_right){// g//	  p//       cRotateL(grandfather);grandfather->_col = RED;parent->_col = BLACK;}else{// g//	  p// cRotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}}_root->_col = BLACK;return true;
}
6、总结

红黑树插入的核心是根据 uncle 的颜色分情况调整

  • uncle为红:通过变色 + 向上递归解决。
  • uncle为黑或不存在:通过旋转 + 变色解决,旋转的目的是调整节点结构,让连续红节点的问题更易解决。

所有调整的最终目的,都是为了维持红黑树的 5 条性质,确保树的近似平衡,从而保证插入、查询、删除的时间复杂度为 O(log n)。


四、红黑树的验证

写完插入操作后,我们怎么知道该树就已经是红黑树了呢?

红黑树的检测分为两步:

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

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

public:bool IsBalance(){return _IsBalance(_root);}void InOrder(){_InOrder(_root);cout << endl;}
private: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;}return CheckColor(root, 0, benchmark);}bool CheckColor(Node* root, int blacknum, int benchmark){if (root == nullptr){if (blacknum != benchmark){return false;}return true;}if (root->_col == BLACK){++blacknum;}if (root->_col == RED && root->_parent && root->_parent->_col == RED){cout << root->_kv.first << "出现连续红色节点" << endl;}return CheckColor(root->_left, blacknum, benchmark)&& CheckColor(root->_right, blacknum, benchmark);}void _InOrder(Node* root){if (root == nullptr){return;}_InOrder(root->_left);cout << root->_kv.first << ":" << root->_kv.second << endl;_InOrder(root->_right);}
http://www.dtcms.com/a/435217.html

相关文章:

  • 制作卖东西网站玩具网站 下载
  • 网站建设培训课程wordpress描述插件
  • php网站超市源码下载十大永久免费crm
  • 网站色彩代码carousel wordpress
  • 帮别人做网站一般app开发费用多少
  • 上海网站建设服务市价编程做网站容易还是做软件
  • Go 语言流程控制详解:if / switch / for
  • 企业网站栏目设计h5手机网站实例
  • 操作系统应用开发(十三)RustDesk文件服务搭建——东方仙盟筑基期
  • 莱州网站建设服务程序开发的步骤是什么
  • 网站域名多少钱一年杭州seo公司排名
  • 武昌网站制作公司深圳vi设计公司推荐
  • AI驱动的软件质量保障:未来已来
  • Lama Cleaner图片去水印工具最新版IOPaint-1.5.3使用教程-优雅草卓伊凡
  • Spring Boot 配置属性绑定
  • tauri中的wry和tao是干啥的?都是什么作用
  • 个人网站建设发布信息wordpress移动端悬浮导航代码
  • 神经网络评估指标:准确率、召回率等详解(代码验证)
  • linux免密切换
  • 藏语自然语言处理入门 - 2 分词
  • 2020年美国新冠肺炎疫情数据分析与可视化
  • 天津重型网站建设推荐影响网站alexa排名的主要因素有
  • 2.Java中创建线程
  • 分段函数的傅里叶变换及其应用
  • 全网网站建设优化长江设计公司
  • 公司网站推广张家界网站建设
  • 英语学习-Saints037
  • 拧紧扭矩到达后电机是否过冲测试(拧紧策略算法系列)
  • 领码方案|微服务与SOA的世纪对话(1):从“大一统”到“小而美”
  • 【JS】什么是单例模式