【高阶数据结构】红黑树 {概念及性质;红黑树的结构;红黑树的实现;红黑树插入操作详细解释;红黑树的验证}

红黑树

一、红黑树的概念

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

在这里插入图片描述

AVL树 VS 红黑树

  • 红黑树是一种特化的AVL树,都是在进行插入和删除操作时通过特定操作保持二叉查找树的平衡,从而获得较高的查找性能。

  • AVL树要求每棵子树的左右高度差不超过1,是严格平衡;而红黑树要求最长路径不超过最短路径的2倍,是接近平衡。

  • 而红黑树是一种AVL树的变体,它要求最长路径不超过最短路径的2倍,左右子树高差有可能大于 1。所以红黑树不是严格意义上的平衡二叉树(AVL),但对之进行平衡的代价较低, 其平均统计性能要强于 AVL

  • 相对而言,插入或删除同样的数据,AVL树旋转的更多,而红黑树则旋转的更少效率相对较高


二、红黑树的性质

红黑树是每个结点都带有颜色属性的二叉查找树,颜色或红色或黑色。 在二叉查找树强制一般要求以外,对于任何有效的红黑树我们增加了如下的额外要求:

  • 性质1. 结点是红色或黑色。

  • 性质2. 根结点是黑色。

  • 性质3. 每个红色结点的两个子结点都是黑色。(每条路径上不能有两个连续的红色结点)

  • 性质4. 从任一结点到其每个叶子的所有路径都包含相同数目的黑色结点。 (每条路径上的黑色节点数量相同)

  • 性质5. 所有NIL结点都是黑色的。(NIL节点即空结点)

这些约束强制了红黑树的关键性质: 从根到叶子的最长的可能路径不多于最短的可能路径的两倍长。结果是这个树大致上是平衡的。因为操作比如插入、删除和查找某个值的最坏情况时间都要求与树的高度成比例,这个在高度上的理论上限允许红黑树在最坏情况下都是高效的,而不同于普通的二叉查找树。

是性质3导致路径上不能有两个连续的红色结点确保了这个结果。最短的可能路径都是黑色结点,最长的可能路径有交替的红色和黑色结点。因为根据性质4所有路径都有相同数目的黑色结点,这就表明了没有路径能多于任何其他路径的两倍长。

思考:新插入的节点应该设为黑色还是红色?

  • 如果将新插入的节点设为黑色,不管插到那条路径都必然违反性质4。

  • 如果将新插入的节点设为红色:如果父节点是红色则违反性质3,需要进行调整;如果父节点是黑色就正常插入,无需调整。

  • 对比两种情况,最终选择将新插入的节点设为红色。


三、STL中的红黑树结构

  • 为了后续实现关联式容器map/set,STL红黑树的实现中增加一个头结点;
  • 因为根节点必须为黑色,为了与根节点进行区分,将头结点给成红色;
  • 并且让头结点的_parent域指向红黑树的根节点,_left域指向红黑树中最小的节点,_right域指向红黑树中最大的节点。

在这里插入图片描述

头结点的作用:

  • STL明确规定,begin()与end()代表的是一段前闭后开的区间,而对红黑树进行中序遍历后,可以得到一个有序的序列,因此:begin()可以放在红黑树中最小节点(即最左侧节点)的位置,end()放在最大节点(最右侧节点)的下一个位置,关键是最大节点的下一个位置在哪块?
  • 能否给成nullptr呢?答案是行不通的,因为对end()位置的迭代器进行–操作,必须要能找最后一个元素,此处就不行,因此最好的方式是将end()放在头结点的位置:

四、核心结构

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 _color; //颜色属性RBTreeNode(const pair<K,V> &kv=pair<K,V>(), Color color = RED):_left(nullptr),_right(nullptr),_parent(nullptr),_kv(kv),_color(color){}
};//红黑树结构
template <class K, class V>
class RBTree{ typedef RBTreeNode<K,V> Node;Node *_phead; //指向头结点的指针public:RBTree(){_phead = new Node; //红黑树的头结点_phead->_left = _phead; //起初先让头结点的左右指针指向自己_phead->_right = _phead;}Node*& GetRoot(){ //返回根节点指针的引用,便于进行修改return _phead->_parent; }//........
private:Node* LeftMost(){ //返回红黑树的最左节点指针Node *root = GetRoot();if(root == nullptr) //如果根节点为空,就返回_pheadreturn _phead;else{Node *left = root;while(left->_left!=nullptr){left = left->_left;}return left;}}Node* RightMost(){ //返回红黑树的最右节点指针Node *root = GetRoot();if(root == nullptr) //如果根节点为空,就返回_pheadreturn _phead;else{Node *right = root;while(right->_right!=nullptr){right = right->_right;}return right;}}//......
};

五、红黑树的插入操作

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

  1. 按照二叉搜索的树规则插入新节点

  2. 检测新节点插入后,红黑树的性质是否造到破坏。因为新节点的默认颜色是红色,因此:

    • 如果新插入的节点是根节点,需要将节点变为黑色以满足性质2。
    • 如果父节点是黑色的,没有违反红黑树的任何性质,则不需要调整;
    • 但如果父节点颜色为红色时,就违反了性质3:路径上不能有两个连续的红色结点。此时需要对红黑树分情况来讨论:

在讲解情况三、四、五之前,先说明一下:

  • cur为当前节点(关注节点),p(parent)为父节点,g(grandparent)为祖父节点,u(uncle)为叔叔节点;
  • cur不一定就是新插入的节点,也有可能是因为 cur 的子树在调整的过程中将 cur 节点的颜色由黑色改成红色。

5.1 情况一:u存在且为红

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

抽象分析:

在这里插入图片描述

  1. 因为cur和p都为红色违反性质3,所以一定要把p变为黑色。
  2. 但只变p又违反性质4各路径上黑色节点的数量不同,所以要把u也变为黑色。
  3. 但原来所有路径上只有1个黑色节点(可见的)而现在变为2个。如果g树是子树,又会使整棵树违反性质4。所以要把g变为红色。
  4. g的父节点也可能是红色,所以要继续向上调整。

解决方式:变色并继续向上调整

  1. 将p,u都改为黑色,g改为红色;
  2. 如果g不为根,就把g当成cur继续向上调整;
  3. 如果g为根,就把g变为黑色。性质2:根节点是黑色的。

具体分析:

cur就是新插入的节点:

在这里插入图片描述

cur节点原来是黑色之后又被调整为红色:

在这里插入图片描述

注意:a,b,c,d,e可能是连续的几层黑色节点(要求每条路径的黑色节点数量相同),然后才出现上述情况。因为情况太多,过于复杂故作省略。


5.2 情况二:u不存在/u存在且为黑(单旋)

情况二: cur为红,p为红,g为黑,u不存在/u存在且为黑(单旋)

抽象分析:
在这里插入图片描述

  1. 因为cur和p都为红色违反性质3,所以一定要把p变为黑色。
  2. 但只变p使左路黑节点+1违反性质4,因此还要以g为轴点右单旋,使左路黑节点-1。
  3. 但此时由于右单旋使右路黑节点+1,所以要将g变为红色,右路黑节点-1。最终满足性质4。

解决方式:单旋+变色

  1. 如果p为g的左孩子,cur为p的左孩子(左左),则对g进行右单旋;
  2. 如果p为g的右孩子,cur为p的右孩子(右右),则对g进行左单旋;
  3. p、g变色–p变黑色,g变红色。
  4. 完成旋转变色后每条路径的黑节点数量相同且与插入前也相同,并且根节点为黑色不需要继续往上处理。

具体分析:u 的情况有两种

uncle节点不存在:

如果 u 节点不存在,则 cur 一定是新插入节点,因为如果 cur 不是新插入节点,则 cur 和 p 一定有一个节点的颜色是黑色,就不满足性质4:每条路径黑色节点个数相同。

在这里插入图片描述

uncle节点存在且为黑色:

如果 u 节点存在且为黑色,那么 cur 节点原来的颜色也一定是黑色的,现在看到其是红色的原因是因为 cur 的子树在调整的过程中将 cur 节点的颜色由黑色改成红色。

在这里插入图片描述

注意:a,b,c,d,e可能是连续的几层黑色节点(要求每条路径的黑色节点数量相同),然后才出现上述情况。因为情况太多,过于复杂故作省略。


5.3 情况三:u不存在/u存在且为黑(双旋)

情况三: cur为红,p为红,g为黑,u不存在/u存在且为黑(双旋)

抽象图:
在这里插入图片描述
情况三先以p为轴点左单旋,转换为情况二。

解决方式:双旋+变色

  1. p为g的左孩子,cur为p的右孩子(左右),则先对p做左单旋,再对g做右单旋;
  2. p为g的右孩子,cur为p的左孩子(右左),则先对p做右单旋,再对g做左单旋;
  3. cur、g变色–cur变黑色,g变红色。
  4. 完成旋转变色后每条路径的黑节点数量相同且与插入前也相同,并且根节点为黑色不需要继续往上处理。

具体分析:

uncle节点不存在

在这里插入图片描述

uncle节点存在且为黑色:

在这里插入图片描述

注意:a,b,c,d,e可能是连续的几层黑色节点(要求每条路径的黑色节点数量相同),然后才出现上述情况。因为情况太多,过于复杂故作省略。

总结:

  • 二叉树插入操作的难点在于通过变色和旋转操作恢复红黑树的性质,性质得到满足红黑树就能做到近似平衡:最长路径不超过最短路径的两倍。
  • 恢复的最终目的:1.关注子树满足红黑树的所有性质 2.插入前后关注子树每条路径的黑节点数量不变(保证整棵树的性质4)

5.4 插入代码

bool Insert(const pair<K,V> &kv)
{//1. 按照二叉搜索的树规则插入新节点Node* &root = GetRoot(); //这里注意要用引用接收返回值if(root == nullptr){//如果新插入的节点是根节点,需要将节点变为黑色以满足性质2root = new Node(kv, BLACK); //因为GetRoot返回指针的引用,所以改的实际是_phead->_parentroot->_parent = _phead;_phead->_left = root;_phead->_right = root;return true;}Node *cur = root;Node *parent = nullptr;while(cur != nullptr){if(kv.first > cur->_kv.first){parent = cur;cur = cur->_right;}else if(kv.first < cur->_kv.first){parent  = cur;cur = cur->_left;}else{return false;}}cur = new Node(kv,RED); //新插入的节点默认是红色的if(kv.first > parent->_kv.first){parent->_right = cur;}else{parent->_left = cur;}cur->_parent = parent;//2.检测新节点插入后,红黑树的性质是否造到破坏。//如果父节点是黑色的,没有违反红黑树的任何性质,则不需要调整;//但如果父节点颜色为红色时,就违反了性质3:路径上不能有两个连续的红色结点。//上一次循环中grandparent 为根节点,此次循环parent == _pheadwhile(parent != _phead && parent->_color == RED) {Node *grandparent = parent->_parent;//断言检查:grandparent一定不为空且为黑色!assert(grandparent != nullptr);assert(grandparent->_color == BLACK);Node *uncle = grandparent->_left;if(parent == grandparent->_left)uncle = grandparent->_right;if(uncle != nullptr && uncle->_color == RED) //情况一:uncle存在且为红{parent->_color = uncle->_color = BLACK; //变色grandparent->_color = RED;cur = grandparent; //继续向上调整parent = cur->_parent;}else //情况二、三:uncle不存在或uncle存在且为黑{if(parent == grandparent->_left){if(cur == parent->_left) //左左{RotateR(grandparent); //右单旋parent->_color = BLACK; //变色grandparent->_color = RED;}else{ //左右RotateL(parent); //左右双旋RotateR(grandparent);cur->_color = BLACK; //变色grandparent->_color = RED;}}else{if(cur == parent->_right) //右右{RotateL(grandparent); //左单旋parent->_color = BLACK; //变色grandparent->_color = RED;}else{ //右左RotateR(parent); //右左双旋RotateL(grandparent);cur->_color = BLACK; //变色grandparent->_color = RED;}}//旋转变色后无需继续调整,直接退出循环。break; } //end of else} //end of while//如果在调整过程中将根节点变为红色,记得重新变回黑色。if(parent == _phead) root->_color = BLACK;//令头节点的左指针指向红黑树的最左节点_phead->_left = LeftMost();//令头节点的右指针指向红黑树的最右节点_phead->_right = RightMost();return true;
}

5.5 旋转代码

void RotateL(Node *parent){Node *subR = parent->_right;Node *subRL = subR->_left;Node *ppNode = parent->_parent;parent->_right = subRL;if(subRL != nullptr){subRL->_parent = parent;}subR->_left = parent;parent->_parent = subR;if(ppNode == _phead){_phead->_parent = subR;}else{if(ppNode->_left == parent){ppNode->_left = subR;}else{ppNode->_right = subR;}}subR->_parent = ppNode;
}void RotateR(Node *parent){Node *subL = parent->_left;Node *subLR = subL->_right;Node *ppNode = parent->_parent;parent->_parent = subL;subL->_right = parent;parent->_left = subLR;if(subLR != nullptr)subLR->_parent = parent;if(ppNode == _phead){ppNode->_parent = subL;}else{if(ppNode->_left == parent){ppNode->_left = subL;}else{ppNode->_right = subL;}}subL->_parent = ppNode;
}

六、查找和遍历

Node* Find(const K &k){Node *root = GetRoot();if(root == nullptr)return nullptr;Node *cur = root;while(cur != nullptr){if(k > cur->_kv.first){cur = cur->_right;}else if(k < cur->_kv.first){cur = cur->_left;}else{return cur;}}return nullptr;
}void Inorder(){_Inorder(GetRoot());cout << endl;
}void _Inorder(Node *root){if(root == nullptr) return; _Inorder(root->_left);cout << root->_kv.first << ":" << root->_kv.second << " ";_Inorder(root->_right);
}

七、红黑树的验证

红黑树的检测分为两步:

  1. 检测其是否满足二叉搜索树(中序遍历是否为有序序列)
  2. 检测其是否满足红黑树的性质
bool IsValidRBTree(){Node *root = GetRoot();//空树也是红黑树if(root == nullptr) return true;//检查性质2:if(root->_color != BLACK){cout << "违反性质2:根节点不为黑色!" << endl;return false;}//检查性质3,4:int benchmark = 0;return _IsValidRBTree(root, 0, benchmark);
}//blacknum:用于记录当前路径的黑色节点个数,不能传引用。
//benchmark:用于记录第一条路径的黑色节点个数。需要传引用,返回给上层递归。
bool _IsValidRBTree(Node *root, int blacknum, int &benchmark){if(root == nullptr){if(benchmark == 0) //表示第一条路径遍历完{benchmark = blacknum; //记录第一条路径的黑色节点个数return true;}else{if(blacknum != benchmark) //如果其他路径的blacknum与第一条路径不同,说明违反性质4{cout << "违反性质4:从任意节点到每个叶子节点的所有路径都包含相同数目的黑色节点!" << endl;return false;}else{return true;}}}//检查性质3:if(root->_color == RED && root->_parent->_color == RED){cout << "违反性质3:路径上有两个连续的红色节点!" << endl;return false;}if(root->_color == BLACK){++blacknum; }return _IsValidRBTree(root->_left, blacknum, benchmark)&& _IsValidRBTree(root->_right, blacknum, benchmark);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/101229.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

框架分析(10)-SQLAlchemy

框架分析&#xff08;10&#xff09;-SQLAlchemy 专栏介绍SQLAlchemy特性分析ORM支持数据库适配器事务支持查询构建器数据库连接池事务管理器数据库迁移特性总结 优缺点优点强大的对象关系映射支持多种数据库灵活的查询语言自动管理数据库连接支持事务管理易于扩展和定制 缺点学…

OJ练习第164题——具有所有最深节点的最小子树

具有所有最深节点的最小子树 力扣链接&#xff1a;865. 具有所有最深节点的最小子树 力扣链接&#xff1a;1123. 最深叶节点的最近公共祖先 题目描述 给定一个根为 root 的二叉树&#xff0c;每个节点的深度是 该节点到根的最短距离 。 返回包含原始树中所有 最深节点 的…

机器学习笔记之最优化理论与方法(五)凸优化问题(上)

机器学习笔记之最优化理论与方法——凸优化问题[上] 引言凸优化问题的基本定义凸优化定义&#xff1a;示例 凸优化与非凸优化问题的区分局部最优解即全局最优解凸优化问题的最优性条件几种特殊凸问题的最优性条件无约束凸优化等式约束凸优化非负约束凸优化 引言 本节将介绍凸优…

芯片开发之难如何破解?龙智诚邀您前往DR IP-SoC China 2023 Day

2023年9月6日&#xff08;周三&#xff09;&#xff0c;龙智即将亮相D&R IP-SoC China 2023 Day&#xff0c;呈现集成了Perforce与Atlassian产品的芯片开发解决方案&#xff0c;助力企业更好、更快地进行芯片开发。 龙智资深顾问、技术支持部门负责人李培将带来主题演讲—…

从零开始,无需公网IP,搭建本地电脑上的个人博客网站并发布到公网

文章目录 前言1. 安装套件软件2. 创建网页运行环境 指定网页输出的端口号3. 让WordPress在所需环境中安装并运行 生成网页4. “装修”个人网站5. 将位于本地电脑上的网页发布到公共互联网上 前言 在现代社会&#xff0c;网络已经成为我们生活离不开的必需品&#xff0c;而纷繁…

2023年9月CSPM-3国标项目管理中级认证报名,找弘博创新

CSPM-3中级项目管理专业人员评价&#xff0c;是中国标准化协会&#xff08;全国项目管理标准化技术委员会秘书处&#xff09;&#xff0c;面向社会开展项目管理专业人员能力的等级证书。旨在构建多层次从业人员培养培训体系&#xff0c;建立健全人才职业能力评价和激励机制的要…

功率信号源可以应用在哪些方面

功率信号源是一种能够产生一定功率的信号源&#xff0c;广泛应用于各个领域。下面将介绍功率信号源在电子、通信、工业和科研等方面的应用。 在电子行业中&#xff0c;功率信号源是一种重要的测试工具。它可以产生各种波形的信号&#xff0c;如正弦波、方波、脉冲波等&#xff…

无涯教程-JavaScript - HEX2BIN函数

描述 HEX2BIN函数将十六进制数转换为二进制数。 语法 HEX2BIN (number, [places])争论 Argument描述Required/Optionalnumber 您要转换的十六进制数。 数字不能超过10个字符(40位)。数字的最高有效位是符号位(从右数第40位)。其余的39位是幅度位。 负数使用二进制补码表示。…

索尼 toio™应用创意开发征文|一步两步三步模拟浇花系统

目录 1.toio™介绍 2、创意分析 2.1 创意设计 2.2 创意落地 3、创意实现 3.1 环境安装 3.2 核心玩法 总结 1.toio™介绍 索尼的toio™是一款启发创意的机器人产品&#xff0c;旨在通过与真实世界的互动&#xff0c;为各年龄段的用户提供娱乐体验。这款产品具有高度的灵…

数学建模-大模型的对比

引用老哥数学建模视频 【ChatGPT 4.0】在数学建模中的应用&#xff01;算法Matlab写作&#xff0c;全面测评六款大模型软件&#xff0c;直接使用&#xff01; 哪些问题可以问GPT 一、算法应用 1帮我总结一下数学建模有哪些预测类算法&#xff1f; 2灰色预测模型级比检验是什么…

OpenCV基础(一):图片加载,图片腐蚀,图片模糊,图片边缘检测,图片保存

前言 在Android音视频开发中&#xff0c;网上知识点过于零碎&#xff0c;自学起来难度非常大&#xff0c;不过音视频大牛Jhuster提出了《Android 音视频从入门到提高 - 任务列表》&#xff0c;结合我自己的工作学习经历&#xff0c;我准备写一个音视频系列blog。本文是音视频系…

用huggingface.Accelerate进行分布式训练

诸神缄默不语-个人CSDN博文目录 本文属于huggingface.transformers全部文档学习笔记博文的一部分。 全文链接&#xff1a;huggingface transformers包 文档学习笔记&#xff08;持续更新ing…&#xff09; 本部分网址&#xff1a;https://huggingface.co/docs/transformers/m…