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

目录

一、红黑树概念

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

插入成功:

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Games104现代游戏引擎笔记 面向数据编程与任务系统

Basics of Parallel Programming 并行编程的基础 核达到了上限&#xff0c;无法越做越快&#xff0c;只能通过更多的核来解决问题 Process 进程 有独立的存储单元&#xff0c;系统去管理&#xff0c;需要通过特殊机制去交换信息 Thread 线程 在进程之内&#xff0c;共享了内存…

如何在 Nginx Proxy Manager(NPM)上部署静态网站

前言 众所周知&#xff0c;我们在之前介绍过 Nginx Proxy Manager&#xff08;以下简称 NPM) 这个反向代理的神器&#xff0c;对于一些 Docker 搭建的 Web 项目&#xff0c;NPM 能够很轻松地给他们做反向代理。 然而对于一些静态网站&#xff0c;小伙伴们可能不知道怎么用 NP…

15分钟,不,用模板做数据可视化只需5分钟

测试显示&#xff0c;一个对奥威BI软件不太熟悉的人来开发数据可视化报表&#xff0c;要15分钟&#xff0c;而当这个人去套用数据可视化模板做报表&#xff0c;只需5分钟&#xff01; 数据可视化模板是奥威BI上的一个特色功能板块。用户下载后更新数据源&#xff0c;立即就能获…

LeetCode【560】和为k的子数组

题目&#xff1a; 思路&#xff1a; 转化为前缀和问题&#xff0c;和为k&#xff0c;即为&#xff1a;前缀和差值为k的情况统计&#xff1b; 为什么要转化为前缀和呢&#xff1f;因为和为k的子数组可能有n个元素&#xff0c;但是前缀和差值为k&#xff0c;只有两个元素&#…

微机原理_9

一、单项选择题(本大题共15小题,每小题3分,共45分。在每小题给出的四个备选项中,选出一个正确的答案。 1.当运算结果的最高位为1时&#xff0c;标志位(&#xff09; A. CF1 B. OF1 C. SF1 D. ZF1 2、汇编语言源程序中,每个语句由四项组成,如语句要完成一定功能,那么该语句中不可…

HTTP1.1协议详解

目录 协议介绍协议的特点存在的问题协议优化方案与HTTP 1.0协议的区别 协议介绍 HTTP 1.1是一种基于文本的互联网实体信息交互协议&#xff0c;是Web上任何数据交换和客户端-服务器交互的基础。它允许获取各种类型的资源&#xff0c;如HTML文档&#xff0c;并支持在互联网上交…

系列三、双亲委派机制

一、概述 当一个类收到了类加载的请求&#xff0c;它首先不会尝试自己去加载这个类&#xff0c;而是把这个请求委派给父类去完成&#xff0c;每一层的类加载器都是如此&#xff0c;因此所有的请求都应该传送到启动类加载器中&#xff0c;只有当父类加载器反馈自己无法完成这个…

arcgis--创建多分辨率DEM

方法一&#xff1a;技术链为【栅格转点】-【创建TIN】-【TIN转栅格】。首先需要将栅格转成点数据&#xff0c;再根据点数据创建TIN&#xff0c;再将TIN转栅格。 1、打开一幅DEM影像图&#xff0c;如下&#xff1a; 在【转换工具】-【由栅格转出】 -【栅格转点】工具中&#xf…

设计模式(4)-行为型模式

行为型模式 行为型模式用于描述程序在运行时复杂的流程控制&#xff0c;即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务&#xff0c;它涉及算法与对象间职责的分配。 行为型模式分为类行为模式和对象行为模式&#xff0c;前者采用继承机制来在类间…

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 …

SpringBoot--中间件技术-4:整合Shiro,Shiro基于会话SessionManager实现分布式认证,附案例含源代码!

SpringBoot整合安全中间件Shiro 技术栈&#xff1a;SpringBootShiro 代码实现 pom文件加坐标 Springboot版本选择2.7.14 &#xff1b;java版本1.8 &#xff1b; shiro做了版本锁定 1.3.2 <properties><java.version>1.8</java.version><!--shiro版本锁定…

鸿蒙:从0到“Hello Harmony”

效果展示 一.概述 明年华为鸿蒙就不再兼容Android生态了&#xff0c;作为拥有7亿终端用户的华为&#xff0c;建立自己的生态也是理所当然。 所以对HarmonyOS的研究也是众多开发者绕不开的坎了。 今天这篇博文主要实现一个“Hello Harmony&#xff01;”的Demo。 二.官方链接…

ChatGLM3-6B:新一代开源双语对话语言模型,流畅对话与低部署门槛再升级

项目设计集合&#xff08;人工智能方向&#xff09;&#xff1a;助力新人快速实战掌握技能、自主完成项目设计升级&#xff0c;提升自身的硬实力&#xff08;不仅限NLP、知识图谱、计算机视觉等领域&#xff09;&#xff1a;汇总有意义的项目设计集合&#xff0c;助力新人快速实…

SystemVerilog学习 (6)——验证平台

一、概述 测试平台&#xff08;Testbench&#xff09;是整个验证系统的总称。它包含了验证系统的各个组件、组件之间的互联关系&#xff0c;测试平台的配置与控制等&#xff0c; 从更系统的意义来讲&#xff0c;它还包括编译仿真的流程、结果分析报告和覆盖率检查等。 从狭义上…

【ArcGIS Pro二次开发】(76):面积平差工具

之前做过一个【三调土地利用现状分类面积汇总】的工具&#xff0c;在流程中使用了面积平差的方法。 考虑了在其它场合可能也需要进行面积平差&#xff0c;因此单独提取出来作为一个工具。 平差实现的方法如下图&#xff1a; 主要的计算过程如上图所示&#xff0c;算出总面积差…

ubuntu下C++调用matplotlibcpp进行画图(超详细)

目录 一、换源 二、安装必要的软件 三、下载matplotlibcpp 四、下载anaconda 1.anaconda下载 2.使用anaconda配置环境 五、下载CLion 1.下载解压CLion 2.替换jbr文件夹 3.安装CLion 4.激活CLion 5.CLion汉化 6.Clion配置 六、使用CLion运行 七、总结 我的环…

总结1057

考研倒计38天 极限冲刺day1 今日共计学习13h33m&#xff0c;为了能走出备考的低谷阶段&#xff0c;来一场与自我的较量。在尽可能保证效率的情况下&#xff0c;玩命干。考研这件事&#xff0c;从来不是因为看到了希望才去努力&#xff0c;而是玩命努力后才看到希望。

通过IP地理位置阻止网络攻击

随着网络技术的不断发展&#xff0c;网络安全问题日益引起人们的关注。网络攻击者往往隐藏在虚拟的网络世界中&#xff0c;难以追踪其真实身份和位置。然而&#xff0c;近年来技术专家们借助IP地址定位的方法来阻止网络被攻击&#xff0c;这种方法引起了广泛关注。本文将探讨通…

posix定时器的使用

POSIX定时器是基于POSIX标准定义的一组函数&#xff0c;用于实现在Linux系统中创建和管理定时器。POSIX定时器提供了一种相对较高的精度&#xff0c;可用于实现毫秒级别的定时功能。 POSIX定时器的主要函数包括&#xff1a; timer_create()&#xff1a;用于创建一个定时器对象…

图解分布式事务实现原理(一)

参考 本文参考https://zhuanlan.zhihu.com/p/648556608&#xff0c;在小徐的基础上做了个人的笔记。 分布式事务场景 事务核心特性 在聊分布式事务之前&#xff0c;我们先理清楚有关于 “事务” 的定义. 事务 Transaction&#xff0c;是一段特殊的执行程序&#xff0c;其需…