【C++】-- 红黑树详解

news2024/11/26 16:48:19

目录

一、红黑树概念

 1.概念 

 2.性质

二、红黑树定义

 1.红黑树节点定义

(1)将新插入节点置为红色

(2)将新插入节点置为黑色

2.红黑树定义

 三、红黑树插入

1.插入节点

2.控制颜色 

(1)父亲为黑色

(2)父亲是红色

四、红黑树颜色处理

1.cur红,p红,g黑,u存在且为红

(1)抽象图

(2)具象图

2. cur为红,p为红,g为黑,u为黑或u不存在(单旋)

(1)抽象图 

(2)具象图 

3.cur为红,p为红,g为黑,u不存在或u为黑(双旋)

(1)抽象图

(2)具象图

4.红黑树节点插入代码

五、红黑树查找

六、红黑树检查是否平衡

七、红黑树遍历

八、红黑树验证 


一、红黑树概念

 1.概念 

红黑树也是一种二叉搜索树,在每个结点上增加一个存储位,来表示该节点的颜色,节点要么是红色要么是黑色。

 2.性质

红黑树具有以下性质:

(1)每个结点不是红色就是黑色

(2)根节点是黑色的

(3)没有连续的红色节点

(4)每条路径上都包含相同数目的黑色节点

由于(3)和(4)互相控制,因此满足以上性质就能保证:最长路径中节点个数不会超过最短路径节点个数的2倍

最短路径:全部由黑色节点构成

最长路径:由一黑一红构成,且红色节点数量等于黑色节点数量。

假设黑色节点有N个,最短路径长度为最长路径长度为2*。但是在红黑树中,不一定有最短路径和最长路径。

二、红黑树定义

 1.红黑树节点定义

红黑树节点相比于AVL树,多了一个颜色,因此需要一个成员变量来存储节点颜色。AVL树用高度严格控制平衡。红黑树近似平衡,所以不需要平衡因子。

但是红黑树节点的构造函数在初始化节点时,肯定要初始化节点颜色的,那么颜色需要一开始初始化成红色,因为初始化成红色,可能破坏规则(3),影响不大。但是假如将节点初始化成黑色,一定会破坏规则(4)。

(1)将新插入节点置为红色

假如将新插入节点置为红色,会有以下两种情况: 

①当父亲是黑色时,没有破坏规则(3),也没有破坏规则(4)

②当父亲是红色时,破坏了规则(3),但是只需要改变一下颜色即可

(2)将新插入节点置为黑色

但是假如将新节点初始化成黑色,不管父亲是黑色还是红色,一定会破坏规则(4),并且影响其他路径,影响范围广。

 ①当父亲是黑色时,破坏了规则(4)

 ②当父亲是红色时,也破坏了规则(4)

 ​​

 

因此节点要初始化成红色。


   
   
  1. //红黑树节点颜色
  2. enum Colour
  3. {
  4. RED,
  5. BLACK,
  6. };
  7. //红黑树节点定义
  8. template< class K, class V>
  9. struct RBTreeNode
  10. {
  11. RBTreeNode<K, V>* _left; //节点的左孩子
  12. RBTreeNode<K, V>* _right; //节点的右孩子
  13. RBTreeNode<K, V>* _parent; //节点的父亲
  14. pair<K,V> _kv; //节点的值
  15. Colour _col; //节点颜色
  16. RBTreeNode( const pair<K, V>& kv)
  17. :_left( nullptr)
  18. ,_right( nullptr)
  19. ,_parent( nullptr)
  20. ,_kv(kv)
  21. ,_col(RED) //节点初始化成红色
  22. {}
  23. };

2.红黑树定义


   
   
  1. template< class K, class V>
  2. class RBTree
  3. {
  4. typedef RBTreeNode<K, V> Node;
  5. //构造函数
  6. RBTree()
  7. :_root( nullptr)
  8. {}
  9. private:
  10. Node* _root;
  11. };

 三、红黑树插入

1.插入节点

插入节点分为2步: 

(1) 先查找,如果树中已存在该节点,则插入失败

(2)树中不存在该节点,插入


   
   
  1. //插入
  2. pair<Node*, bool> Insert(const pair<K, V>& kv)
  3. {
  4. if (_root == nullptr)
  5. {
  6. _root = new Node(kv);
  7. _root->_col = BLACK;
  8. return make_pair(_root, true);
  9. }
  10. //1.先看树中,kv是否存在
  11. Node* parent = nullptr;
  12. Node* cur = _root;
  13. while (cur)
  14. {
  15. if (cur->_kv.first < kv.first)
  16. {
  17. //kv比当前节点值大,向右走
  18. parent = cur;
  19. cur = cur->_right;
  20. }
  21. else if (cur->_kv.first > kv.first)
  22. {
  23. //kv比当前节点值小,向左走
  24. parent = cur;
  25. cur = cur->_left;
  26. }
  27. else
  28. {
  29. //kv和当前节点值相等,已存在,插入失败
  30. return make_pair(cur, false);
  31. }
  32. }
  33. //2.走到这里,说明kv在树中不存在,需要插入kv,并且cur已经为空,parent已经是叶子节点了
  34. Node* newNode = new Node(kv);
  35. newNode->_col = RED;
  36. if (parent->_kv.first < kv.first)
  37. {
  38. //kv比parent值大,插入到parent的右边
  39. parent->_right = newNode;
  40. cur->_parent = parent;
  41. }
  42. else
  43. {
  44. //kv比parent值小,插入到parent的左边
  45. parent->_left = newNode;
  46. cur->_parent = parent;
  47. }
  48. cur = newNode;
  49. return make_pair(cur, true);
  50. }

2.控制颜色 

所以在插入节点之后,为了满足红黑树的性质,还需要调整节点颜色:

(1)父亲为黑色

如果父亲是黑色,那么不需要调整,4个规则都满足,插入已完成

(2)父亲是红色

如果父亲是红色,违反了规则(3),需要调整颜色

 这时就需要对树中的节点进行颜色处理了。

四、红黑树颜色处理

 所有插入的新节点颜色都是红色,当父亲是红色时,需要进行颜色处理,分3种情况进行处理。在这3种情况中,cur为新插入节点,p为父亲节点,u为叔叔节点,g为祖父节点。下面的树都可能是一棵完整的树,也有可能是一棵子树。

1.cur红,p红,g黑,u存在且为红

当cur为红,p为红,g为黑,u存在且为红时,为了满足红黑树的性质,处理方法:将p和u变黑,g变红

(1)抽象图

如下a、b、c、d、e都是子树:

(1)假如g是根节点,根据红黑树性质,根节点必须是黑色,那就把g再变黑就好了

(2)假如g不是根节点,g是子树,那么g还有parent节点。

①如果g的parent是黑色,满足红黑树性质,那么停止调整。

②如果g的parent是红色,就破坏了规则(3),那么还需要继续向上调整。

 调整方法为:把g当作cur,继续向上调整,直到p不存在,或p存在且为黑停止。

(2)具象图

①g是根节点,直接把p和u变黑,g变红

②g不是根节点,g是子树,把p和u变黑,g变红之后,还要继续向上调整,直到p不存在,或p存在且为黑停止

2. cur为红,p为红,g为黑,u为黑或u不存在(单旋)

这种情况下,g、p、cur形成直线,先看cur为红,p为红,g为黑,u为黑的情况

这是由情况一cur红,p红,g黑,u存在且为红处理以后变换而来,比如以下情况:

 在这种情况下,cur原来的颜色一定是黑色,只不过在处理的过程当中,将cur的颜色变成了红色,所以cur不是新增节点,而是新增节点的祖先节点。

(1)抽象图 

 如下a、b、c、d、e都是子树,由于要旋转,所以要分为两种情况:当p是g的左子树,cur是p的左子树时,g右单旋,p变黑,g变红:

当p是g的右子树,cur是p的右子树时,g左单旋,p变黑,g变红: 

(2)具象图 

cur是新增节点的祖先节点,那么a、b一定不为空,由于从g到u的路径上有2个黑色节点,那么a和b都存在一个黑色节点,因此,c中也有一个黑色节点,才能满足每条路径上有相同数目的黑色节点。因此d、e要么同时不存在,要么同时为空。

当p是g的左子树,cur是p的左子树时,将节点g右单旋,p变黑,g变红:

 

当p是g的右子树,cur是p的右子树时,将节点g左单旋,p变黑,g变红:

再看cur为红,p为红,g为黑,u不存在的情况:

u不存在的情况更为简单,假如p是g的左子树,cur是p的左子树,将节点g右单旋,p变黑,g变红即可 

​​​​​​​​​​​​​​

 假如p是g的右子树,cur是p的右子树,将节点g左单旋,p变黑,g变红即可 

3.cur为红,p为红,g为黑,u不存在或u为黑(双旋)

这种情况下g、p、cur形成折线,先看cur为红,p为红,g为黑,u为黑的情况:

(1)抽象图

当p是g的左子树,cur是p的右子树时,处理方法:p左单旋,g右单旋,cur变黑,g变红

 当p是g的右子树,cur是p的左子树时,处理方法:p右单旋,就变成了情况二

(2)具象图

 当p是g的左子树,cur是p的右子树时,将p左单旋,g右单旋,cur变黑,g变红

 当p是g的右子树,cur是p的左子树,p右单旋,g左单旋,p变黑,g变红

再看cur为红,p为红,g为黑,u不存在的情况,u不存在的情况更为简单:

当p是g的左子树,cur是p的右子树时, 将p左单旋,g右单旋,cur变黑,g变红

当p是g的右子树,cur是p的左子树,p右单旋就变成了情况二:

4.红黑树节点插入代码


   
   
  1. //插入
  2. pair<Node*, bool> Insert(const pair<K, V>& kv)
  3. {
  4. if (_root == nullptr)
  5. {
  6. _root = new Node(kv);
  7. _root->_col = BLACK;
  8. return make_pair(_root, true);
  9. }
  10. //1.先看树中,kv是否存在
  11. Node* parent = nullptr;
  12. Node* cur = _root;
  13. while (cur)
  14. {
  15. if (cur->_kv.first < kv.first)
  16. {
  17. //kv比当前节点值大,向右走
  18. parent = cur;
  19. cur = cur->_right;
  20. }
  21. else if (cur->_kv.first > kv.first)
  22. {
  23. //kv比当前节点值小,向左走
  24. parent = cur;
  25. cur = cur->_left;
  26. }
  27. else
  28. {
  29. //kv和当前节点值相等,已存在,插入失败
  30. return make_pair(cur, false);
  31. }
  32. }
  33. //2.走到这里,说明kv在树中不存在,需要插入kv,并且cur已经为空,parent已经是叶子节点了
  34. Node* newNode = new Node(kv);
  35. newNode->_col = RED;
  36. if (parent->_kv.first < kv.first)
  37. {
  38. //kv比parent值大,插入到parent的右边
  39. parent->_right = newNode;
  40. cur->_parent = parent;
  41. }
  42. else
  43. {
  44. //kv比parent值小,插入到parent的左边
  45. parent->_left = newNode;
  46. cur->_parent = parent;
  47. }
  48. cur = newNode;
  49. //如果父亲存在,且父亲颜色为红就要处理
  50. while (parent && parent->_col == RED)
  51. {
  52. //关键看叔叔
  53. Node* grandfather = parent->_parent;
  54. if (parent == grandfather->_left) //父亲是祖父的左子树
  55. {
  56. Node* uncle = grandfather->_right;
  57. //情况一:叔叔存在且为红
  58. if (uncle->_col == RED)
  59. {
  60. parent->_col = uncle->_col = BLACK;
  61. grandfather->_col = RED;
  62. //继续向上调整
  63. cur = grandfather;
  64. parent = cur->_parent;
  65. }
  66. else //情况二+情况三:叔叔不存在或叔叔存在且为黑
  67. {
  68. //情况二:单旋
  69. if (cur == parent->_left)
  70. {
  71. RotateR(grandfather);
  72. parent->_col = BLACK;
  73. grandfather->_col = RED;
  74. }
  75. else //情况三:双旋
  76. {
  77. RotateL(parent);
  78. RotateR(grandfather);
  79. cur->_col = BLACK;
  80. grandfather->_col = RED;
  81. }
  82. break; //插入结束
  83. }
  84. }
  85. else //父亲是祖父的右子树
  86. {
  87. Node* uncle = grandfather->_left;
  88. //情况一:叔叔存在且为红
  89. if (uncle && uncle->_col == RED)
  90. {
  91. parent->_col = uncle->_col = BLACK;
  92. grandfather->_col = RED;
  93. //继续往上调整
  94. cur = grandfather;
  95. parent = grandfather->_parent;
  96. }
  97. else //情况二+情况三:叔叔不存在或叔叔存在且为黑
  98. {
  99. //情况二:单旋
  100. if (cur == parent->_right)
  101. {
  102. RotateL(grandfather);
  103. parent->_col = BLACK;
  104. grandfather->_col = RED;
  105. }
  106. else //情况三:双旋
  107. {
  108. RotateR(parent);
  109. RotateL(grandfather);
  110. cur->_col = BLACK;
  111. grandfather->_col = RED;
  112. }
  113. break; //插入结束
  114. }
  115. }
  116. }
  117. _root->_col = BLACK;
  118. return make_pair(newNode, true);
  119. }

 五、红黑树查找

查找比较简单,递归向左走或向右走:


   
   
  1. //查找
  2. Node* Find(const K& key)
  3. {
  4. Node* cur = _root;
  5. while (cur)
  6. {
  7. if (cur->_kv.first < key) //key比当前节点小,就向右查找
  8. {
  9. cur = cur->_right;
  10. }
  11. else if (cur->_kv.first > key) //key比当前节点大,就向左查找
  12. {
  13. cur = cur->_left;
  14. }
  15. else //找到了
  16. {
  17. return cur;
  18. }
  19. }
  20. return nullptr; //空树,直接返回
  21. }

六、红黑树检查是否平衡

 检查是否平衡还是要用到递归子函数

(1)需要判断是否满足红黑树的4条性质

(2)计算最左路径上的黑色节点个数,递归路径时,需要计算该条路径上的黑色节点个数是否和最左路径上的节点个数相等


   
   
  1. bool _CheckBalance(Node* root, int blackNum, int count)
  2. {
  3. if (root == nullptr)
  4. {
  5. if (count != blackNum)
  6. {
  7. cout << "黑色节点数量不相等" << endl;
  8. return false;
  9. }
  10. return true;
  11. }
  12. if (root->_col == RED && root->_parent->_col == RED)
  13. {
  14. cout << "存在连续红色节点" << endl;
  15. return false;
  16. }
  17. if (root->_col == BLACK)
  18. {
  19. count++;
  20. }
  21. return _CheckBalance(root->_left, blackNum, count)
  22. && _CheckBalance(root->_right, blackNum, count);
  23. }
  24. //检查是否平衡
  25. bool CheckBlance()
  26. {
  27. if (_root == nullptr)
  28. {
  29. return true;
  30. }
  31. if (_root->_col == RED)
  32. {
  33. cout << "根节点为红色" << endl;
  34. return false;
  35. }
  36. //找最左路径做黑色节点数量参考值
  37. int blackNum = 0;
  38. Node* left = _root;
  39. while (left)
  40. {
  41. if (left->_col == BLACK)
  42. {
  43. blackNum++;
  44. }
  45. left = left->_left;
  46. }
  47. int count = 0;
  48. return _CheckBlance(_root, blackNum, count);
  49. }

对于以下红黑树,递归过程如下:

七、红黑树遍历

遍历也很简单,中序递归遍历左子树和右子树:


   
   
  1. //遍历
  2. void _InOrder(Node* root)
  3. {
  4. if (root == nullptr)
  5. {
  6. return;
  7. }
  8. _InOrder(root->_left);
  9. cout << root->_kv.first << ":" << root->_kv.second << endl;
  10. _InOrder(root->_right);
  11. }
  12. void InOrder()
  13. {
  14. _InOrder(_root);
  15. cout << endl;
  16. }

八、红黑树验证 


   
   
  1. #include "RedBlackTree.h"
  2. void TestRBTree()
  3. {
  4. //,1,3,5,15,7,16,14
  5. int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16 };
  6. RBTree< int, int> t;
  7. for ( auto e : a)
  8. {
  9. t. Insert( make_pair(e, e));
  10. }
  11. t. InOrder();
  12. //cout << t.CheckBalance() << endl;
  13. }
  14. int main()
  15. {
  16. TestRBTree();
  17. return 0;
  18. }

插入成功:

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何使用 Github Action 管理 Issue

本文作者为 360 奇舞团前端开发工程师 Daryl 前言 很多小伙伴打开 github 上的仓库都只使用Code查看代码&#xff0c;或者只是把 github 当成一个代码仓库&#xff0c;但是 github 还提供了很多好用的功能。 其中&#xff0c;GitHub Action就是一个很好用的功能&#xff0c;本文…

EfficientNet:通过模型效率彻底改变深度学习

一、介绍 EfficientNet 是深度学习领域的里程碑&#xff0c;代表了神经网络架构方法的范式转变。EfficientNet 由 Google Research 的 Mingxing Tan 和 Quoc V. Le 开发&#xff0c;在不影响性能的情况下满足了对计算高效模型不断增长的需求。本文深入探讨了 EfficientNet 背后…

自动驾驶汽车:人工智能最具挑战性的任务

据说&#xff0c;自动驾驶汽车是汽车行业梦寐以求的状态&#xff0c;将彻底改变交通运输业。就在几年前&#xff0c;对自动驾驶汽车的炒作风靡一时&#xff0c;那么到底发生了什么呢&#xff1f;这么多公司吹嘘到2021年我们将迎来的无人驾驶汽车革命在何处&#xff1f;事实证明…

LeetCode(18)整数转罗马数字【数组/字符串】【中等】

目录 1.题目2.答案3.提交结果截图 链接&#xff1a; 12. 整数转罗马数字 1.题目 罗马数字包含以下七种字符&#xff1a; I&#xff0c; V&#xff0c; X&#xff0c; L&#xff0c;C&#xff0c;D 和 M。 字符 数值 I 1 V 5 X …

javaweb---maventomcat使用教程

文章目录 今日内容0 复习昨日1 Maven1.0 引言1.1 介绍1.2 下载1.3 安装1.3.1 解压1.3.2 配置环境变量1.3.3 测试 1.4 仓库1.5 Maven配置1.5.1 修改仓库位置1.5.2 设置镜像 2 IDEA - MAVEN2.1 idea关联maven2.2 创建java项目2.3 java项目结构2.4 pom2.5 导入依赖2.5.1 查找依赖2…

【Hello Go】Go语言基础类型

Go语言基础类型 基础类型命名变量变量声明变量初始化变量赋值匿名变量 常量字面常量常量定义iota枚举 基础数据类型分类 fmt包的标准输入输出格式说明输入类型转换类型取别名 基础类型 命名 Go语言中的命名遵循下面的几个规则 必须以字母或者是下划线开头不能使用Go语言中的…

C/C++---------------LeetCode第1394.找出数组中的幸运数

找出数组中的幸运数 题目及要求暴力算法哈希算法在main里使用 题目及要求 在整数数组中&#xff0c;如果一个整数的出现频次和它的数值大小相等&#xff0c;我们就称这个整数为「幸运数」。 给你一个整数数组 arr&#xff0c;请你从中找出并返回一个幸运数。 如果数组中存在…

过滤器模式 rust和java的实现

文章目录 过滤器模式实现 过滤器模式实现javarustjavarust rust代码仓库 过滤器模式 过滤器模式&#xff08;Filter Pattern&#xff09;或标准模式&#xff08;Criteria Pattern&#xff09;是一种设计模式&#xff0c;这种模式允许开发人员使用不同的标准来过滤一组对象&…

C++ opencv基本用法【学习笔记(九)】

这篇博客为修改过后的转载&#xff0c;因为没有转载链接&#xff0c;所以选了原创 文章目录 一、vs code 结合Cmake debug1.1 配置tasks.json1.2 配置launch.json 二、图片、视频、摄像头读取显示2.1 读取图片并显示2.2 读取视频文件并显示2.3 读取摄像头并写入文件 三、图片基…

C# 之 选择并调用文件[winform]

winform 之 选择并调用文件 在 form.cs[设计] 文件中选择一个button, 然后设置一个点击函数 将下方内容复制到函数中执行 private void push_btn_Click(object sender, EventArgs e){ // 1. 打开文件管理器选择文件OpenFileDialog openFileDialog1 new OpenFileDialog(); /…

【数电】IEEE754浮点数

IEEE754浮点数 1.组成及分类2.计算(1)符号位(2)阶码(3)尾码(4)实际计算公式 1.组成及分类 &#xff08;1&#xff09;组成 IEEE754浮点数由三部分组成&#xff1a;符号位、阶码和尾码。 &#xff08;2&#xff09;分类 根据数据位宽分为三类&#xff1a;短浮点数、长浮点数和…

PHP项目学习笔记-萤火商城-增加一个模块(表涉及到的操作和文件)

背景 是在store的后台添加一个页面&#xff0c;显示的如满意度调查的页面 在router.config.js里面配置一个新的菜单 路径&#xff1a;yoshop2.0-store\src\config\router.config.js 代码如下&#xff0c;很简单&#xff0c;定义了这菜单点击的时候进入的页面&#xff0c;和下面…

Donut 中,video组件层级失效、同层渲染失效、z-index设置无效解决办法

微信小程序转安卓之后&#xff0c;z-index设置的层级关系失效&#xff0c;video组件总是处在最上层解决办法&#xff1a; 很重要的设置! 同层渲染要开 xweb&#xff0c;project.miniapp.json中勾选此设置 感谢腾讯官方大佬 黄嘉敏

【Git】的分支与版本

前言 Git 的分支是指将代码库从某一个特定的提交记录开始的一个独立的开发线&#xff0c;也可以理解为是一种代码开发的并行方式。分支在 Git 中的使用非常广泛&#xff0c;它可以让多人在同一个代码库中并行开发&#xff0c;同时也能够很方便地进行代码版本控制和管理。 Git …

PM2学习

目录 PM2简介 pm2的主要特性 PM2安装 启动PM2项目 查看应用列表&#xff08;查看当前机器执行的所有进程&#xff09; 查看某个应用详情 重启 停止 删除 日志查看 负载均衡 监控CPU/内存 内存使用超过上限自动重启 监听代码变化/自动重启 PM2简介 PM2是常用的node…

什么是OpenCL?

什么是OpenCL&#xff1f; 1.概述 OpenCL(Open Computing Language 开放计算语言)是一种开放的、免版税的标准&#xff0c;用于超级计算机、云服务器、个人计算机、移动设备和嵌入式平台中各种加速器的跨平台并行编程。OpenCL是由Khronos Group创建和管理的。OpenCL使应用程序…

modbus-RTU是一种比较简单、可靠的协议

modbus-RTU是一种比较简单、可靠的协议 RTU, 是modbus中的一种应用层协议&#xff0c;在OSI的第七层 数据格式 应用

[C国演义] 第二十章

第二十章 最长回文子序列让字符串成为回文串的最少插入次数 最长回文子序列 力扣链接 单个数组讨论子序列 ⇒ dp[i] -- 以nums[i]为结尾的所有子序列中, 回文子序列的最长长度. 然后讨论 最后一个位置的归属情况 但 又要满足 回文结构 ⇒ 二维dp ⇒ dp[i][j] -- 区间[i, j]内…

类加载器(classloader)

作者&#xff1a;ZeaTalk 链接&#xff1a;https://www.zhihu.com/question/49667892/answer/690161827 来源&#xff1a;知乎 著作权归作者所有。商业转载请联系作者获得授权&#xff0c;非商业转载请注明出处。 类加载器&#xff08;classloader&#xff09; 先从类加载器…

【数据结构】直接插入排序

&#x1f466;个人主页&#xff1a;Weraphael ✍&#x1f3fb;作者简介&#xff1a;目前正在学习c和算法 ✈️专栏&#xff1a;数据结构 &#x1f40b; 希望大家多多支持&#xff0c;咱一起进步&#xff01;&#x1f601; 如果文章有啥瑕疵 希望大佬指点一二 如果文章对你有帮助…