经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » C++ » 查看文章
C++数据结构红黑树全面分析
来源:jb51  时间:2022/2/28 13:24:24  对本文有异议

??博客代码已上传至gitee:https://gitee.com/byte-binxin/cpp-class-code

??概念和性质

红黑树的概念: 红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。它是通过控制节点颜色的方式来控制这棵树的相对平衡,保证了没有一条路径会比其它路径长出两倍。

红黑树的性质:

  • 结点是红色或黑色。
  • 根结点是黑色。
  • 所有叶子都是黑色。(叶子是空结点)
  • 每个红色结点的两个子结点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色结点)
  • 从任一节结点到其每个叶子的所有路径都包含相同数目的黑色结点

上面的五个性质还可以用更通俗的语言描述为三句话:

  • 根节点是黑色的
  • 没有连续的红节点
  • 每条路径的黑节点数目相同(每条路径指的是从根节点到黑色的空节点)

思考: 为什么红黑树中最长路径的长度不会超过最短路径节点个数的两倍?

最长路径: 该条路径上节点分布是一红一黑

最短路径: 该条路径上节点分布是全黑

假设每条路径黑色节点数为N,则最长路径为2N,最短路径为N,所以这样就推出红黑树中最长路径的长度不会超过最短路径节点个数的两倍。

??红黑树的实现

??红黑树节点定义

这里也是一个三叉链,其中每个节点包含颜色的元素在里面:

  1. enum Color
  2. {
  3. RED,
  4. BLACK
  5. };
  6.  
  7. template<class K, class V>
  8. struct RBTreeNode
  9. {
  10. RBTreeNode<K, V>* _left;
  11. RBTreeNode<K, V>* _right;
  12. RBTreeNode<K, V>* _parent;
  13.  
  14. pair<K, V> _kv;
  15. Color _color;
  16.  
  17. RBTreeNode(const pair<K, V>& kv, Color color = RED)
  18. :_left(nullptr)
  19. , _right(nullptr)
  20. , _parent(nullptr)
  21. , _kv(kv)
  22. , _color(color)
  23. {}
  24. };

??红黑树结构定义

里面只包含一个根节点的成员变量,和前面两棵树的结构定义没有什么大的区别,区别在于节点的定义:

  1. template<class K, class V>
  2. class RBTree
  3. {
  4. typedef RBTreeNode<K, V> Node;
  5. public:
  6. private:
  7. Node* _root = nullptr;
  8. };

??红黑树的插入

??方法概述

第一步: 我们先按照二叉搜索树树插入节点的方式,插入节点

第二步: 为了不破坏红黑树的规则,我们插入节点后要对红黑树进行相应的调整

思考: 我们插入节点应该默认插入红色节点好还是黑色节点好呢? 答案是红色节点。为什么呢?我们要考虑哪种方式对红黑树的破坏性更大一点。如果是黑色,此时黑导致该条路径比其它路径上黑色节点数都多一个,这样下来调整红黑树的步骤代价似乎会有点大;如果是红色,不会破坏规则5,只是破坏规则4,可能会出现连续的红色节点,这样我们只需要调整该节点及其附近节点的颜色即可,代价没有前一种方式大,所以我们选择第二种方式。

??调整节点颜色

第一种情况: 如果插入节点的父亲是黑色节点,那么可以直接结束,不需要继续调整了

第二种情况: 如果插入节点为的父亲为红色节点,就需要进行相应的调整 下面讨论父亲节点是祖父节点的左孩子的几种情形(是右孩子的情形和这个类型,大家可以自己推演一下,这里我们把父亲节点叫做p(parent),祖父叫g(grandfather),叔叔节点叫u(uncle)):

情况1: p为红色(g肯定是存在的且为黑),u存在且为红

操作: 把p和u改成黑,g改成红,如果g为根节点就把g的颜色变成黑然后结束,如果g不为根节点,且g的父亲为黑色也节数,为红色就需要迭代向上调整,判断此时处于何种情况 具像图:

如果g的父亲为红,就迭代向上调整:cur = grandfather,grandfather = grandfather->parent

抽象图:抽象图中cur可能是新插入的节点,也可能是迭代调整上来的节点,这里g这棵子树每条路径黑色节点数是相同的,且调整后g这棵子树的每条路径黑色数相同且和之前一样。cur是parent的左孩子和右孩子是一样的,因为这里都是对颜色进行控制,和方向无关。

情况2: p为红色(g肯定是存在的且为黑),u不存在

操作: cur为parent的左孩子时,对g进行右单旋,然后将p的颜色改黑,g的颜色改红;cur为parent的右孩子时,先对p进行左单旋,然后对g进行右单旋,然后将cur的颜色改黑,g的颜色改红 具象图:此时cur一定为新增节点,因为g的右子树没有黑节点,所以cur的下面也不可能有黑节点 cur为parent的左孩子时 一条直线,此时进行右单旋

cur为parent的左孩子时 一条折线,此时进行左右双旋

上面的第二种情况可以先进行左单旋,然后交换cur和p,把折线变为直线,最后都执行直线的情况。

情况3: p为红色(g肯定是存在的且为黑),u存在且为黑

操作: 如果cur为parent的左孩子,对g进行右单旋,然后将p的颜色改为黑,g的颜色改为红;如果cur为parent的右孩子,先对p进行左单旋,对g进行右单旋,然后将cur的颜色改为黑,g的颜色改为红

解释: 假设此时a和b中黑色节点数为a,c的黑色节点数也一定为a,d和e的黑色节点数就是a-1,调整后cur和g的抽象图的黑色节点都是a,整体相等。 抽象图:此时cur一定为调整上来的节点,因为如果是新增节点的话,那么原来g这棵子树左右黑色节点数目不等,所以cur一定是调整上来的节点。 cur为parent的左孩子 一条直线,进行右单旋即可

cur为parent的右孩子 一条折线,此时进行左右双旋

和情况2一样,上面的第二种情况可以先进行左单旋,然后交换cur和p,把折线变为直线,最后都执行直线的情况。

总结: 上面就是p是g的左孩子的所有情形,为g的右孩子是与这个类似。还有注意的是根节点最后一定要改为黑色。

??插入代码实现

旋转代码如下: 这里就是上一篇博客的旋转代码,具体如下

  1. // 左单旋
  2. void RotateL(Node* parent)
  3. {
  4. Node* subR = parent->_right;
  5. Node* subRL = subR->_left;
  6.  
  7. // parent的右指向subR的左
  8. parent->_right = subRL;
  9.  
  10. if (subRL) subRL->_parent = parent;
  11.  
  12. Node* ppNode = parent->_parent;
  13. parent->_parent = subR;
  14. subR->_left = parent;
  15.  
  16. if (ppNode == nullptr)
  17. {
  18. _root = subR;
  19. subR->_parent = nullptr;
  20. }
  21. else
  22. {
  23. if (ppNode->_left == parent)
  24. ppNode->_left = subR;
  25. else
  26. ppNode->_right = subR;
  27.  
  28. subR->_parent = ppNode;
  29. }
  30. }
  31. // 右单旋
  32. void RotateR(Node* parent)
  33. {
  34. Node* subL = parent->_left;
  35. Node* subLR = subL->_right;
  36.  
  37. // parent的左指向subL的右
  38. parent->_left = subLR;
  39.  
  40. if (subLR) subLR->_parent = parent;
  41.  
  42. Node* ppNode = parent->_parent;
  43. parent->_parent = subL;
  44. subL->_right = parent;
  45.  
  46. if (ppNode == nullptr)
  47. {
  48. _root = subL;
  49. subL->_parent = nullptr;
  50. }
  51. else
  52. {
  53. if (ppNode->_left == parent)
  54. ppNode->_left = subL;
  55. else
  56. ppNode->_right = subL;
  57.  
  58. subL->_parent = ppNode;
  59. }
  60. }

插入代码实现如下:

  1. pair<Node*, bool> Insert(const pair<K, V>& kv)
  2. {
  3. if (_root == nullptr)
  4. {
  5. _root = new Node(kv, BLACK);// 根节点默认给黑
  6. return make_pair(_root, true);
  7. }
  8.  
  9. Node* cur = _root;
  10. Node* parent = nullptr;
  11.  
  12. while (cur)
  13. {
  14. parent = cur;
  15. if (kv.first < cur->_kv.first)
  16. cur = cur->_left;
  17. else if (kv.first > cur->_kv.first)
  18. cur = cur->_right;
  19. else
  20. return make_pair(nullptr, false);
  21. }
  22. // 节点默认给红节点,带来的影响更小
  23. // 给黑节点的话会影响 每条路径的黑节点相同这条规则
  24. cur = new Node(kv);
  25. if (cur->_kv.first < parent->_kv.first)
  26. {
  27. parent->_left = cur;
  28. cur->_parent = parent;
  29. }
  30. else
  31. {
  32. parent->_right = cur;
  33. cur->_parent = parent;
  34. }
  35.  
  36. // 调整颜色
  37. // 情况一:p是红,g是黑,u存在且为红
  38. // 调整后的几种情况:
  39. // 1.如果g为根节点,把g的颜色改成黑,结束;
  40. // 2.如果g不为根节点,
  41. // a.g的父节点为黑,结束;
  42. // b.g的父节点为红,迭代向上调整,继续判断是哪种情况(一和三)
  43. // cur = grandfather;
  44. // father = cur->father;
  45. // 这里不管cur是在p的左边还是右边,都是一样的,关心的是颜色而不是位置
  46. // 情况二:p是红,g是黑,u不存在/u为黑 cur p g 三个是一条直线
  47. // 调整方法(左边为例):1.右单旋 2.把p改成黑,g改成红
  48. // a. u不存在时,cur必定是新增节点
  49. // b. u存在时,cur必定是更新上来的节点
  50. // 情况三:p是红,g是黑,u不存在/u为黑 cur p g 三个是一条折线
  51. // 调整方法(左边为例):1.p左单旋 2.g右单旋 3.把cur改成黑,g改成红
  52. // a. u不存在时,cur必定是新增节点
  53. // b. u存在时,cur必定是更新上来的节点
  54. while (parent && parent->_color == RED)
  55. {
  56. Node* grandfather = parent->_parent;
  57. // 左边
  58. if (grandfather->_left == parent)
  59. {
  60. // 红黑色的条件关键看叔叔
  61. Node* uncle = grandfather->_right;
  62. // u存在且为红
  63. if (uncle && uncle->_color == RED)
  64. {
  65. // 调整 p和u改成黑,g改成红
  66. parent->_color = uncle->_color = BLACK;
  67. grandfather->_color = RED;
  68.  
  69. // 迭代 向上调整
  70. cur = grandfather;
  71. parent = cur->_parent;
  72. }
  73. else// u存在为黑/u不存在
  74. {
  75. // 折线用一个左单旋处理 1.p左单旋 2.g右单旋 3.把cur改成黑,g改成红 cur p g 三个是一条折线
  76. if (cur == parent->_right)
  77. {
  78. RotateL(parent);
  79. swap(parent, cur);
  80. }
  81. // 直线 cur p g 把p改成黑,g改成红
  82. // 右单旋 有可能是第三种情况
  83. RotateR(grandfather);
  84.  
  85. parent->_color = BLACK;
  86. grandfather->_color = RED;
  87. }
  88. }
  89. // uncle在左边
  90. else
  91. {
  92. Node* uncle = grandfather->_left;
  93. if (uncle && uncle->_color == RED)
  94. {
  95. parent->_color = uncle->_color = BLACK;
  96. grandfather->_color = RED;
  97.  
  98. // 迭代 向上调整
  99. cur = grandfather;
  100. parent = cur->_parent;
  101. }
  102. else
  103. {
  104. // 折线用一个右单旋处理 g p cur g变红p边黑
  105. if (cur == parent->_left)
  106. {
  107. RotateR(parent);
  108. swap(parent, cur);
  109. }
  110.  
  111. // 直线 g p cur 把p改成黑,g改成红
  112. // 左单旋 有可能是第三种情况
  113. RotateL(grandfather);
  114.  
  115. parent->_color = BLACK;
  116. grandfather->_color = RED;
  117. }
  118. }
  119. }
  120.  
  121. _root->_color = BLACK;
  122. return Find(kv.first);
  123. }

??红黑树的删除

??方法概述

第一步: 先按照二叉搜索树删除节点的方式找到要删除节点(也可能是替代节点)

第二步: 然后为了不破坏红黑树的几条规则,要对节点的颜色进行相应地调整

??调整颜色

第一种情况: 删除节点(也可能是替代节点)(之后都叫delNode),如果该节点为红色,则直接删除退出即可,delNode没找到也可以直接退出

第二种情况: delNode为黑色(最多只有一个孩子且为红色,因为替代节点最多只有一个孩子),delNode有一个孩子时,删除delNode节点,然后把孩子节点的颜色改成黑色,也可直接结束

第三种情况: delNode为黑色,且没有孩子时,有下面几种情况(兄弟节点叫b(brother),父亲节点叫p(parent))(下面是cur是parent的左孩子的情形,右孩子的情形和它类似,不介绍):

情况1: p为黑,b为红,两个孩子存在且一定为黑

操作: 对p进行左单旋,然后将p的颜色改成红,b的颜色改成黑

分析: 调整之前抽象三角形的黑色节点都是a,因为cur下面有一个节点要被删除,所以cur下面少了一个黑色节点,也就是p的左边少了一个黑色节点,调整后b两边的黑色节点数不变,cur下面黑色节点数还是少了一个,但是它的兄弟是黑色节点,后面可以通过对cur进行检索,使用其它情况解决这个问题。

抽象图:

情况2: p为红,b为黑,b的两个孩子存在且一定为黑

操作: 把p的颜色改成黑,b的颜色改成红

分析: 调整前,p左边少了一个黑色的节点,调整后,p的左边补上了一个黑色节点,且p的右边的黑色节点数不变,这里可以结束

抽象图:

情况3: p为黑色,且b为黑色,b的两个孩子为黑

操作: 把b的颜色改为红

分析: 调整之前,p左边是缺少一个黑色节点的,调整后,两边黑色节点数相同,但是此时p的右边也少了一个黑色节点,此时p的父亲g,g的右边是比左边多一个黑色节点的,所以需要迭代向上调整,把cur变成p,继续对cur进行检索

抽象图:

情况4: p为任意颜色,b的颜色为黑,b的右孩子为红色

操作: 对p进行左单旋,然后交换p和b的颜色,并把b的颜色改成黑

分析: 调整前,a和b的黑色节点数都是x,c,d,e的黑色节点个数为x+1,也就是p的左边少了一个黑色的节点,调整后,p两边的黑色节点都是x+1,b两边的黑色节点都是x+2,整体都调整好了,所以这里可以结束

抽象图:

情况5: p为任意颜色,b的颜色为黑,b的左孩子为红色

操作: 先对b进行右单旋,然后把b改红,bL改黑,然后对p进行左单旋,然后交换p和b的颜色,并把b的颜色改成黑(情况4)

分析: 和情况四其实是一样的,情况4的b和bR是直线,这里是折线,要通过右单旋变成直线,然后就转为情况4

抽象图:

总结: 删除就是以上几种情况,一般是左边少一个黑色节点,就靠右边补一个,结束,或者右边减少一个,然后两边整体少一个,对父亲节点进行检索。

??删除代码实现

代码实现如下:

  1. bool Erase(const K& key)
  2. {
  3. // 如果树为空,删除失败
  4. if (_root == nullptr)
  5. return false;
  6.  
  7. Node* parent = nullptr;
  8. Node* cur = _root;
  9. Node* delNode = nullptr;
  10. Node* delNodeParent = nullptr;
  11. while (cur)
  12. {
  13. // 小于往左边走
  14. if (key < cur->_kv.first)
  15. {
  16. parent = cur;
  17. cur = cur->_left;
  18. }
  19. else if (key > cur->_kv.first)
  20. {
  21. parent = cur;
  22. cur = cur->_right;
  23. }
  24. else
  25. {
  26. // 找到了,开始删除
  27. // 1.左右子树都为空 直接删除 可以归类为左为空
  28. // 2.左右子树只有一边为空 左为空,父亲指向我的右,右为空,父亲指向我的左
  29. // 3.左右子树都不为空 取左子树最大的节点或右子树最小的节点和要删除的节点交换,然后再删除
  30. if (cur->_left == nullptr)
  31. {
  32. // 要删除节点为根节点时,直接把右子树的根节点赋值给——root
  33. // 根节点的话会导致parent为nullptr
  34. if (_root == cur)
  35. {
  36. _root = _root->_right;
  37. if (_root)
  38. {
  39. _root->_parent = nullptr;
  40. _root->_color = BLACK;
  41. }
  42. return true;
  43. }
  44. else
  45. {
  46. delNode = cur;
  47. delNodeParent = parent;
  48. }
  49. }
  50. else if (cur->_right == nullptr)
  51. {
  52. if (_root == cur)
  53. {
  54. _root = _root->_left;
  55. if (_root)
  56. {
  57. _root->_parent = nullptr;
  58. _root->_color = BLACK;
  59. }
  60. return true;
  61. }
  62. else
  63. {
  64. delNode = cur;
  65. delNodeParent = parent;
  66. }
  67. }
  68. else
  69. {
  70. // 找右子树中最小的节点
  71. Node* rightMinParent = cur;
  72. Node* rightMin = cur->_right;// 去右子树找
  73. while (rightMin->_left)
  74. {
  75. rightMinParent = rightMin;
  76. rightMin = rightMin->_left;
  77. }
  78. //swap(cur->_key, rightMin->_key);
  79. // 替代删除
  80. cur->_kv = rightMin->_kv;
  81.  
  82. delNode = rightMin;
  83. delNodeParent = rightMinParent;
  84. }
  85. break;
  86. }
  87. }
  88. // 没找到
  89. if (cur == nullptr)
  90. return false;
  91. // 1.替代节点为红,直接删除(看上面)
  92. // 2.替代节点为黑(只能有一个孩子或两个孩子)
  93. // i)替代节点有一个孩子不为空(该孩子一定为红),把孩子的颜色改成黑
  94. // ii)替代节点的两个孩子都为空
  95. cur = delNode;
  96. parent = delNodeParent;
  97. if (cur->_color == BLACK)
  98. {
  99. if (cur->_left)// 左孩子不为空
  100. {
  101. cur->_left->_color = BLACK;
  102. }
  103. else if (cur->_right)
  104. {
  105. cur->_right->_color = BLACK;
  106. }
  107. else// 替代节点的两个孩子都为空
  108. {
  109. while (parent)
  110. {
  111. // cur是parent的左
  112. if (cur == parent->_left)
  113. {
  114. Node* brother = parent->_right;
  115. // p为黑
  116. if (parent->_color == BLACK)
  117. {
  118. Node* bL = brother->_left;
  119. Node* bR = brother->_right;
  120. // SL和SR一定存在且为黑
  121. if (brother->_color == RED)// b为红,SL和SR都为黑 b的颜色改黑,p的颜色改红 情况a
  122. {
  123. RotateL(parent);
  124. brother->_color = BLACK;
  125. parent->_color = RED;
  126.  
  127. // 没有结束,还要对cur进行检索
  128. }
  129. else if(bL && bR && bL->_color == BLACK && bR->_color == BLACK)// b为黑,孩子存在
  130. {
  131. // 且孩子也为黑 把brother改成红色,迭代 GP比GU小1 情况b
  132. brother->_color = RED;
  133. cur = parent;
  134. parent = parent->_parent;
  135. }
  136. // bL存在为红,bR不存在或bR为黑 情况e 右旋后变色转为情况d
  137. else if (bL && bL->_color == RED && (!bR || (bR && bR->_color == BLACK)))
  138. {
  139. RotateR(brother);
  140. bL->_color = BLACK;
  141. brother->_color = RED;
  142. }
  143. else if (bR && bR->_color == RED) // 右孩子为红,进行一个左旋,然后把右孩子的颜色改成黑色 情况d
  144. {
  145. RotateL(parent);
  146. swap(brother->_color, parent->_color);
  147. bR->_color = BLACK;
  148. break;
  149. }
  150. else
  151. {
  152. // cur p b 都是黑,且b无孩子,迭代更新
  153. // parent是红就结束
  154. brother->_color = RED;
  155. cur = parent;
  156. parent = parent->_parent;
  157. }
  158. }
  159. // p为红 b一定为黑
  160. else
  161. {
  162. Node* bL = brother->_left;
  163. Node* bR = brother->_right;
  164. if (bL && bR && bL->_color == BLACK && bR->_color == BLACK)// b的孩子全为黑 情况c p变黑,b变红 结束
  165. {
  166. brother->_color = RED;
  167. parent->_color = BLACK;
  168. break;
  169. }
  170. // bL存在为红,bR不存在或bR为黑 情况e 右旋后变色转为情况d
  171. else if (bL && bL->_color == RED && (!bR || (bR && bR->_color == BLACK)))
  172. {
  173. RotateR(brother);
  174. bL->_color = BLACK;
  175. brother->_color = RED;
  176. }
  177. else if (bR && bR->_color == RED) // 右孩子为红,进行一个左旋,然后把右孩子的颜色改成黑色 情况d
  178. {
  179. RotateL(parent);
  180. //swap(brother->_color, parent->_color);
  181. brother->_color = parent->_color;
  182. parent->_color = BLACK;
  183. bR->_color = BLACK;
  184. break;
  185. }
  186. else// cur 为黑,p为红,b为黑 调整颜色,结束
  187. {
  188. parent->_color = BLACK;
  189. brother->_color = RED;
  190. break;
  191. }
  192. }
  193. }
  194. else
  195. {
  196. Node* brother = parent->_left;
  197. // p为黑
  198. if (parent->_color == BLACK)
  199. {
  200. Node* bL = brother->_left;
  201. Node* bR = brother->_right;
  202. // SL和SR一定存在且为黑
  203. if (brother->_color == RED)// b为红,SL和SR都为黑 b的颜色改黑,p的颜色改红 情况a
  204. {
  205. RotateR(parent);
  206. brother->_color = BLACK;
  207. parent->_color = RED;
  208.  
  209. // 没有结束,还要对cur进行检索
  210. }
  211. else if (bL && bR && bL->_color == BLACK && bR->_color == BLACK)// b为黑,孩子存在
  212. {
  213. // 且孩子也为黑 把brother改成红色,迭代 GP比GU小1 情况b
  214. brother->_color = RED;
  215. cur = parent;
  216. parent = parent->_parent;
  217. }
  218. // 右孩子存在且为红,但左孩子不存在或为黑 情况e 右旋后变色转为情况d
  219. else if (bR && bR->_color == RED && (!bL || (bL && bL->_color == BLACK)))
  220. {
  221. RotateL(brother);
  222. brother->_color = RED;
  223. bR->_color = BLACK;
  224. }
  225. else if (bL && bL->_color == RED) // 左孩子为红,进行一个右旋,然后把左孩子的颜色改成黑色 情况d
  226. {
  227. RotateR(parent);
  228. swap(brother->_color, parent->_color);
  229. bL->_color = BLACK;
  230. break;
  231. }
  232. else
  233. {
  234. // cur p b 都是黑,且b无孩子,迭代更新
  235. // if (parent == _root) // p是根节点,把b变红 否则迭代
  236. brother->_color = RED;
  237. cur = parent;
  238. parent = parent->_parent;
  239. }
  240. }
  241. // p为红 b一定为黑
  242. else
  243. {
  244. Node* bL = brother->_left;
  245. Node* bR = brother->_right;
  246. if (bL && bR && bL->_color == BLACK && bR->_color == BLACK)// b的孩子全为黑 情况c p变黑,b变红 结束
  247. {
  248. brother->_color = RED;
  249. parent->_color = BLACK;
  250. break;
  251. }
  252. // 右孩子存在且为红,但左孩子不存在或为黑 情况e 右旋后变色转为情况d
  253. else if (bR && bR->_color == RED && (!bL || (bL && bL->_color == BLACK)))
  254. {
  255. RotateL(brother);
  256. brother->_color = RED;
  257. bR->_color = BLACK;
  258. }
  259. else if (bL && bL->_color == RED) // 左孩子为红,进行一个右旋,然后把左孩子的颜色改成黑色 情况d
  260. {
  261. RotateR(parent);
  262. // swap(brother->_color, parent->_color);
  263. brother->_color = parent->_color;
  264. parent->_color = BLACK;
  265. bL->_color = BLACK;
  266. break;
  267. }
  268. else// cur 为黑,p为红,b为黑 调整颜色,结束
  269. {
  270. parent->_color = BLACK;
  271. brother->_color = RED;
  272. break;
  273. }
  274. }
  275. }
  276. }
  277.  
  278. }
  279. }
  280. delNodeParent = delNode->_parent;
  281. // 删除
  282. if (delNode->_left == nullptr)
  283. {
  284. if (delNodeParent->_left == delNode)
  285. delNodeParent->_left = delNode->_right;
  286. else
  287. delNodeParent->_right = delNode->_right;
  288. if (delNode->_right)// 右不为空,就让右节点的父指针指向delNodeParent
  289. delNode->_right->_parent = delNodeParent;
  290. }
  291. else
  292. {
  293. if (delNodeParent->_left == delNode)
  294. delNodeParent->_left = delNode->_left;
  295. else
  296. delNodeParent->_right = delNode->_left;
  297. if (delNode->_left)// 右不为空,就让右节点的父指针指向delNodeParent
  298. delNode->_left->_parent = delNodeParent;
  299. }
  300.  
  301. delete delNode;
  302. delNode = nullptr;
  303. return true;
  304. }

??红黑树的查找

这里比较简单,直接上代码:

  1. bool Find(const K& key)
  2. {
  3. if (_root == nullptr)
  4. return false;
  5.  
  6. Node* cur = _root;
  7. while (cur)
  8. {
  9. // 小于往左走
  10. if (key < cur->_kv.first)
  11. {
  12. cur = cur->_left;
  13. }
  14. // 大于往右走
  15. else if (key > cur->_kv.first)
  16. {
  17. cur = cur->_right;
  18. }
  19. else
  20. {
  21. // 找到了
  22. return true;
  23. }
  24. }
  25.  
  26. return false;
  27. }

??红黑树的验证

这里通过递归计算出每条路径的节点个数来进行比较,同时验证其他的性质是否符合,从而验证是否红黑树:

  1. bool IsValidRBTree()
  2. {
  3. // 空树也是红黑树
  4. if (_root == nullptr)
  5. return true;
  6. // 判断根节点的颜色是否为黑色
  7. if (_root->_color != BLACK)
  8. {
  9. cout << "违反红黑树的根节点为黑色的规则" << endl;
  10. return false;
  11. }
  12.  
  13. // 计算出任意一条路径的黑色节点个数
  14. size_t blackCount = 0;
  15. Node* cur = _root;
  16. while (cur)
  17. {
  18. if (cur->_color == BLACK)
  19. ++blackCount;
  20. cur = cur->_left;
  21. }
  22.  
  23. // 检测每条路径黑节点个数是否相同 第二个参数记录路径中黑节点的个数
  24. return _IsValidRBTree(_root, 0, blackCount);
  25. }
  26. bool _IsValidRBTree(Node* root, size_t k, size_t blackCount)
  27. {
  28. // 走到空就判断该条路径的黑节点是否等于blackCount
  29. if (root == nullptr)
  30. {
  31. if (k != blackCount)
  32. {
  33. cout << "违反每条路径黑节点个数相同的规则" << endl;
  34. return false;
  35. }
  36. return true;
  37. }
  38.  
  39. if (root->_color == BLACK)
  40. ++k;
  41. // 判断是否出现了连续两个红色节点
  42. Node* parent = root->_parent;
  43. if (parent && root->_color == RED && parent->_color == RED)
  44. {
  45. cout << "违反了不能出现连续两个红色节点的规则" << endl;
  46. return false;
  47. }
  48.  
  49. return _IsValidRBTree(root->_left, k, blackCount)
  50. && _IsValidRBTree(root->_right, k, blackCount);
  51. }

实例演示:

  1. void TestRBTree()
  2. {
  3. //srand((size_t)time(nullptr));
  4. RBTree<int, int> rbt;
  5. // int a[] = { 1,2,3,4,5,6,7,8,9,10,5,7,8,11,12,13 };
  6. // int a[] = { 16,3,7,11,9,26,18,14,15 };
  7. // int a[] = { 4,2,6,1,3,5,15,7,16,14 };
  8. // int a[] = { 10,9,8,7,6,5,4,3,2,1 };
  9. vector<int> a;
  10. for (size_t i = 0; i < 13; ++i)
  11. {
  12. // a.push_back(rand());
  13. a.push_back(i);
  14. }
  15. //int a[] = { 4,2,6,7,3,5 };
  16. /*vector<int> v;
  17. v.reserve(100000);
  18.  
  19. for (size_t i = 1; i <= v.capacity(); ++i)
  20. {
  21. v.push_back(i);
  22. }*/
  23. for (auto e : a)
  24. {
  25. int begin = clock();
  26. rbt.Insert(make_pair(e, e));
  27. int end = clock();
  28. cout << "插入数据 " << e << " 后:" << "树的高度:" << rbt.Height() << " 是否为红黑树:" << rbt.IsValidRBTree();
  29. cout << " 用时:" << end - begin << "ms" << endl;
  30. }
  31. cout << "-------------------------------------------------------" << endl;
  32. for (auto e : a)
  33. {
  34. int begin = clock();
  35. rbt.Erase(e);
  36. int end = clock();
  37. cout << "删除数据 " << e << " 后:" << "树的高度:" << rbt.Height() << " 是否为红黑树:" << rbt.IsValidRBTree();
  38. cout << " 用时:" << end - begin << "ms" << endl;
  39. }
  40. // cout << rbt.IsValidRBTree() << endl;
  41. // rbt.InOrder();
  42. }

代码运行结果如下:

??AVL树和红黑树的比较

 AVL树红黑树
如何控制平衡通过条件平衡因子,子树左右高度差不超过1用过颜色控制,使得最长路径不超出最短路径的长度的两倍
增删查改的时间复杂度可以稳定在O(logN)基本是O(logN),极端情况下是O(log2N)

总结: AVL树是严格意义上的平衡,红黑树是相对的平衡,两者都很高效,但后者用的更多,因为它旋转更是,实现相对简单,付出的代价少一点。

??总结

二叉搜索树的内容就介绍到这,接下来我会给大家介绍map和set两个容器,它们的底层就是红黑树,我会用红黑树给大家模拟实现它们。喜欢的话,欢迎点赞支持和关注~

到此这篇关于C++数据结构红黑树全面分析的文章就介绍到这了,更多相关C++ 红黑树内容请搜索w3xue以前的文章或继续浏览下面的相关文章希望大家以后多多支持w3xue!

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号