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

插入成功:

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

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

相关文章

某头部通信企业:SDLC+模糊测试,保障数实融合安全发展

某头部通信企业是全球领先的综合通信信息解决方案提供商&#xff0c;为全球电信运营商、政企客户和消费者提供创新的技术与产品解决方案。该企业持续关注核心技术攻关&#xff0c;深入打造系列化标杆项目和价值场景&#xff0c;加强数字化平台的推广应用&#xff0c;加快共建开…

Zookeeper学习笔记(1)—— 基础知识

Zookeeper概述 Zookeeper 是一个开源的分布式的&#xff0c;为分布式框架提供协调服务的 Apache 项目 工作机制 Zookeeper从设计模式角度来理解&#xff1a;是一个基于观察者模式设计的分布式服务管理框架&#xff0c;它负责存储和管理大家都关心的数据&#xff0c;然后接受…

LeetCode【560】和为k的子数组

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

蒙HarmonyOS从零实现类微信app效果第二篇,我的+发现页面实现

本着不拖更的原则&#xff0c;今天上新了&#xff0c;今天实现了类微信app的发现页和我的页面。先看效果。 效果是不是看着还不错。其实这两个页面功能实现还是比较简单的&#xff0c;接下来还是老规矩&#xff0c;先进行页面的拆分和代码实现&#xff0c;然后进行相关我认为比…

java初探之代理模式

代理模式 代理模式一般有三种角色&#xff1a; 没有使用代理模式的话可能就会直接去操作真实的对象 加入代理模式就是加入了 隔离 把我们的真实对象与调用者隔离了一下(代理对象) 代理对象的好处&#xff1f; 使用者(client)跟真实的对象是没有直接的交集的。不会直接操作到…

基于平衡优化器算法优化概率神经网络PNN的分类预测 - 附代码

基于平衡优化器算法优化概率神经网络PNN的分类预测 - 附代码 文章目录 基于平衡优化器算法优化概率神经网络PNN的分类预测 - 附代码1.PNN网络概述2.变压器故障诊街系统相关背景2.1 模型建立 3.基于平衡优化器优化的PNN网络5.测试结果6.参考文献7.Matlab代码 摘要&#xff1a;针…

构造函数和初始化列表的关系和区别【详解】

构造函数和初始化列表关系和区别&#xff0c;以及为什么有初始化列表&#xff0c;和它的好处 一、构造函数和初始化列表的关系和区别二、为什么有初始化列表三、使用初始化列表的好处 一、构造函数和初始化列表的关系和区别 百度百科这样定义初始化列表&#xff1a;与其他函数…

CocosCreator3.8神秘面纱 CocosCreator 项目结构说明及编辑器的简单使用

我们通过Dashboard 创建一个2d项目&#xff0c;来演示CocosCreator 的项目结构。 等待创建完成后&#xff0c;会得到以下项目工程&#xff1a; 一、assets文件夹 assets文件夹&#xff1a;为资源目录&#xff0c;用来存储所有的本地资源&#xff0c;如各种图片&#xff0c;脚本…

nodejs+vue黄河风景线旅游网站的设计与实现-微信小程序-安卓-python-PHP-计算机毕业设计

本文首先对该系统进行了详细地描述&#xff0c;然后对该系统进行了详细的描述。管理人员增加了系统首页、个人中心、用户管理、景点分类管理、景点简介管理、旅游路线管理、文章分类管理、公告文章管理、系统管理理等功能。这套黄河风景线旅游网站是根据当前的现实需要&#xf…

Nginx配置开启HTTPS

获取证书文件 Nginx 开启SSL server {listen 443 default ssl;server_name localhost;#charset koi8-r;#access_log logs/host.access.log main;proxy_set_header Host $host;ssl_certificate /usr/local/nginx/cert/server.pem;ssl_certificate_key /usr/local/ngin…

大数据爬虫分析基于Python+Django旅游大数据分析系统

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 基于Python和Django的旅游大数据分析系统是一种使用Python编程语言和Django框架开发的系统&#xff0c;用于处理和分…

OpenAI与微软合作,构建 ChatGPT 5 模型;10天准确天气预报

&#x1f989; AI新闻 &#x1f680; OpenAI与微软合作&#xff0c;构建 ChatGPT 5 模型&#xff0c;下一代人工智能或拥有超级智能 摘要&#xff1a;OpenAI首席执行官 Sam Altman 在接受采访时表示&#xff0c;OpenAI正在与微软合作构建下一代人工智能模型 ChatGPT 5&#x…