当前位置:   article > 正文

C++:红黑树_c++ 红黑树

c++ 红黑树

红黑树的概念

红黑树是一棵二叉搜索树,但是红黑树通过增加一个存储位表示结点的颜色RED或BLACK。通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出2倍,因而是接近平衡的。

 红黑树的性质

⭐1.每个节点不是红色就是黑色。

⭐2.根节点是黑色的。

⭐3.如果一个节点是红色的,则它的两个孩子结点是黑色的。也就意味着,红黑树没有连续的红色节点。

⭐4.对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点。也就是说每条路径都有相同数量的黑色节点。

⭐5.每个叶子结点都是黑色的(此处的叶子结点指的是空结点。

从性质上分析红黑树的近似平衡

一颗红黑树最短的路径是这条路径全黑。最长是一红一黑相间路径。

对于近似平衡来说:

①最优情况就是左右平衡,此时每条路径都是全黑或者是一红一黑相间,形成满二叉树

②差的情况就是左右越不平衡,情况就越差。比如左子树全黑,而右子树是一黑一红相间。假设左子树全黑的路径长度位h = logN,因为红黑树要求每条路径的黑色节点的数量是相同的,而右子树是一红一黑相间的,那就说明右子树的长度是左子树的两倍h = 2*logN,这是最差的情况了,再差点就不是红黑树了。

红黑树节点的定义

红黑树节点的定义,跟AVL树的区别就是红黑树不需要平衡因子,而加入了颜色红跟黑。在定义当中,构造函数初始化列表对颜色_col默认初始化为红色是因为权衡了上面所述红黑树性质中的性质3和性质4。

性质3是说明了红黑树没有连续的红色节点,性质4说明的是每条路径都有相同数量的黑色节点。我们在定义节点的时候,需要给节点的颜色初始化,要么是红色,要么是黑色。

如果我们选择了黑色,那么在插入新节点之前,我们是往红黑树插入的嘛,本身就是红黑树,再插入一个黑色节点的话,那么必定会破坏掉红黑树的规则,是一定被破坏掉的,那么就一定需要对这颗红黑树进行调整。

如果我们选择红色,那么有可能会破坏掉红黑树的规则,也可能不会造成破坏。因为新增的节点的父节点是黑色的,那么就不会造成影响,而父节点是红色的话,那就需要调整。

因此,综上所述,默认初始化为红色是比较好的选择。

  1. //使用枚举
  2. enum Colour
  3. {
  4. RED,
  5. BLACK,
  6. };
  7. template<class K,class V>
  8. struct RBTreeNode
  9. {
  10. pair<K, V> _kv;
  11. RBTreeNode<K, V>* _left;
  12. RBTreeNode<K, V>* _right;
  13. RBTreeNode<K, V>* _parent;
  14. Colour _col;
  15. RBTreeNode(const pair<K,V>& kv)
  16. :_kv(kv)
  17. ,_left(nullptr)
  18. ,_right(nullptr)
  19. ,_parent(nullptr) //跟AVL树一样,使用parent节点是为了旋转
  20. ,_col(RED) //默认是红色
  21. {}
  22. };

红黑色的插入操作

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

第一步:按照二叉搜索树的规则插入新节点。

第二步:检测新节点插入后,红黑树的性质是否造到破坏。

判断依据:因为新节点的默认颜色是红色,因此:如果其双亲节点的颜色是黑色,没有违反红黑树任何性质,则不需要调整;但当新插入节点的双亲节点颜色为红色时,就违反了性质三不能有连在一起的红色节点,此时需要对红黑树分情况来讨论:

本文约定好:cur为当前节点,p(parent)为父节点,g(grandther)为祖父节点,u(uncle)为叔叔节点。

①cur为红,p为红,g为黑,u存在且为红

这种情况是插入的节点cur是红色的(默认),cur的双亲节点和叔叔节点是红色的,祖先节点是黑色的。因为不能连续存在红色节点,那么就需要把颜色调整一下即可,不需要旋转。

调整的方法为:将p节点和u节点的颜色改成黑色,同时为了防止g不是一棵单独的树,先把它变成红色,再进行判断,如果是单独的树,那么就把g的颜色变回黑色,如果是一棵子树,那么就往上继续调整。

②cur为红,p为红,g为黑,u不存在/u存在且为黑

这种情况下是需要旋转+变色的。因为当cur为红色,p为红色,g为黑色,而u的情况是:

如果u不存在,cur肯定是新增的节点,因为在新增之前,p和g组成的树是一棵红黑树,在cur新增之后,不符合红黑树的性质。

这种情况下光凭变色是解决不了问题的,因为u为空说明有一条路径只有根节点,而另一条路径上会存在连续红,只凭变色,无论怎么变,都会导致各路径的黑色节点数量不相同,所以需要先根据p和cur的位置来决定旋转的方向而旋转,再变色。

如果u存在,则u是黑色,并且cur原本的颜色一定是黑色的,而现在cur的颜色是红色,那就肯定是第一种情况调整后,导致了现在的cur的颜色变了

这种情况下就跟u不存在的情况一样,采用旋转+变色来解决问题。此时,u存在不存在已经没什么关系了(单独是看构造红黑树的情况来说)。

③cur为红,p为红,g为黑,u不存在/u存在且为黑

这种情况的颜色是跟情况二的一样,区别就是节点的位置不一样。

这种情况是跟第二中情况差不多,就是多了一步旋转,先左旋再右旋,或者先右旋再左旋。

整体代码如下:

  1. template<class K,class V>
  2. class RBTree
  3. {
  4. typedef RBTreeNode<K, V> Node;
  5. public:
  6. bool Insert(const pair<K, V>& kv)
  7. {
  8. //先按二叉搜索树的规矩来创建一棵二叉搜索树
  9. if (_root==nullptr)
  10. {
  11. _root = new Node(kv);
  12. //因为红黑树的根节点是黑色的
  13. _root->_col = BLACK;
  14. return true;
  15. }
  16. Node* parent = nullptr;
  17. Node* cur = _root;
  18. while (cur)
  19. {
  20. if (cur->_kv.first < kv.first)
  21. {
  22. parent = cur;
  23. cur = cur->_right;
  24. }
  25. else if (cur->_kv.first > kv.first)
  26. {
  27. parent = cur;
  28. cur = cur->_left;
  29. }
  30. else
  31. {
  32. return false;
  33. }
  34. }
  35. cur = new Node(kv);
  36. cur->_col = RED;//多写一步,防止写错代码。
  37. if (parent->_kv.first < kv.first)
  38. {
  39. parent->_right = cur;
  40. cur->_parent = parent;
  41. }
  42. else
  43. {
  44. parent->_left = cur;
  45. cur->_parent = parent;
  46. }
  47. //创建完二叉搜索树
  48. //开始创建红黑树,使用颜色来判断是否需要调整
  49. //循环往上走,循环条件:当走到的parent不为空,并且parent是红色的
  50. //即我们列举是三种情况,parent都是红的,就需要重新调整
  51. //如果parent是黑色的,那就不需要了。直接就是一棵红黑树,不进入循环
  52. while (parent && parent->_col == RED)
  53. {
  54. //保存祖先节点,即g节点
  55. Node* grandfther = parent->_parent;
  56. //判断父节点是在祖先节点的哪边
  57. if (parent == grandfther->_left)
  58. {
  59. //父节点在左边,那么叔叔节点就在右边
  60. Node* uncle = grandfther->_right;
  61. //情况一:uncle存在且为红。改变颜色即可
  62. if (uncle && uncle->_col == RED)
  63. {
  64. //变色。
  65. parent->_col = uncle->_col = BLACK;
  66. grandfther->_col = RED;
  67. //往上走
  68. cur = grandfther;
  69. parent = cur->_parent;
  70. }
  71. else //uncle不存在 或者 存在但是黑色
  72. {
  73. //情况二 p是g的左孩子,cur是p的左孩子,以g为轴右单旋
  74. if (cur == parent->_left)
  75. {
  76. //右单旋
  77. RotateR(grandfther);
  78. //变色 右单旋后,parent为根节点,变黑色。cur和g节点为红色
  79. parent->_col = BLACK;
  80. grandfther->_col = RED;
  81. }
  82. else //情况三 p是g的左孩子,cur是p的右孩子.
  83. {
  84. //先以p为轴左旋转
  85. RotateL(parent);
  86. //变成情况二,再以g为轴右单旋
  87. RotateR(grandfther);
  88. //变色 cur变成根节点,为黑色。p和g是红色
  89. cur->_col = BLACK;
  90. grandfther->_col = RED;
  91. }
  92. break;
  93. }
  94. }
  95. else //parent是在grandfther的右边
  96. {
  97. //叔叔节点就在祖先节点的左边
  98. Node* uncle = grandfther->_left;
  99. //情况一:uncle存在且为红。改变颜色即可
  100. if (uncle && uncle->_col == RED)
  101. {
  102. //变色。
  103. parent->_col = uncle->_col = BLACK;
  104. grandfther->_col = RED;
  105. //往上走
  106. cur = grandfther;
  107. parent = cur->_parent;
  108. }
  109. else //uncle不存在 或者 存在但是黑色
  110. {
  111. //情况二 p是g的右孩子,cur是p的右孩子。
  112. if (cur == parent->_right)
  113. {
  114. //左单旋
  115. RotateL(grandfther);
  116. //变色 右单旋后,parent为根节点,变黑色。cur和g节点为红色
  117. parent->_col = BLACK;
  118. grandfther->_col = RED;
  119. }
  120. else //情况三 p是g的右孩子,cur是p的左孩子.
  121. {
  122. //先以p为轴右旋转
  123. RotateR(parent);
  124. //变成情况二,再以g为轴左单旋
  125. RotateL(grandfther);
  126. //变色 cur变成根节点,为黑色。p和g是红色
  127. cur->_col = BLACK;
  128. grandfther->_col = RED;
  129. }
  130. break;
  131. }
  132. }
  133. }
  134. //最后将根节点置为黑
  135. _root->_col = BLACK;
  136. return true;
  137. }
  138. void RotateR(Node* parent)
  139. {
  140. Node* subL = parent->_left;
  141. Node* subLR = subL->_right;
  142. parent->_left = subLR;
  143. if (subLR)
  144. {
  145. subLR->_parent = parent;
  146. }
  147. Node* ppNode = parent->_parent;
  148. subL->_right = parent;
  149. parent->_parent = subL;
  150. if (ppNode == nullptr)
  151. {
  152. _root = subL;
  153. _root->_parent = nullptr;
  154. }
  155. else
  156. {
  157. if (ppNode->_left == parent)
  158. {
  159. ppNode->_left = subL;
  160. }
  161. else
  162. {
  163. ppNode->_right = subL;
  164. }
  165. subL->_parent = ppNode;
  166. }
  167. }
  168. void RotateL(Node* parent)
  169. {
  170. Node* subR = parent->_right;
  171. Node* subRL = subR->_left;
  172. parent->_right = subRL;
  173. if (subRL)
  174. {
  175. subRL->_parent = parent;
  176. }
  177. Node* ppNode = parent->_parent;
  178. subR->_left = parent;
  179. parent->_parent = subR;
  180. if (ppNode == nullptr)
  181. {
  182. _root = subR;
  183. _root->_parent = nullptr;
  184. }
  185. else
  186. {
  187. if (ppNode->_left == parent)
  188. {
  189. ppNode->_left = subR;
  190. }
  191. else
  192. {
  193. ppNode->_right = subR;
  194. }
  195. subR->_parent = ppNode;
  196. }
  197. }
  198. private:
  199. Node* _root = nullptr;
  200. };

旋转请看:AVL树这篇文章有详细解析。红黑树的旋转直接复用AVL树的旋转的代码即可。

验证红黑树

红黑树的验证分两步:①通过中序遍历验证其是否满足二叉搜索树的性质。②验证是否满足红黑树的性质。

中序遍历:

  1. void Inorder()
  2. {
  3. _Inorder(_root);
  4. }
  5. void _Inorder(Node* root)
  6. {
  7. if (root == nullptr)
  8. return;
  9. _Inorder(root->_left);
  10. cout << root->_kv.first << ": " << root->_kv.second << endl;
  11. _Inorder(root->_right);
  12. }

验证红黑树的性质:

验证思路:

①首先判断根节点的颜色是否为黑色,如果不是黑色,那就直接否定false。这一步是在验证红黑树的性质之一:根的颜色是黑色的。

②接着随便选一条路径并统计这条路径上的黑色节点的数量。

③拿着统计好的结果,去遍历其它路径各自的黑色节点的数量是否与这个结果相等,但凡有一条结果不一样,直接false这一步是在验证红黑树的性质之一:每条路径上的黑色节点是数量是相等的。

④在验证③的过程中,如果发现当前节点与父节点都是红色的,直接false。这一步是验证红黑树的性质之一:不能出现连续的红色节点。

代码如下:

  1. //遍历其它路径
  2. bool Check(Node* root, int blackNum, const int ref)
  3. {
  4. //当走到空节点后,就可以对比数量
  5. if (root == nullptr)
  6. {
  7. //选择打印数量,看看结果对不对
  8. cout << blackNum << endl;
  9. //如果不相等,false
  10. if (blackNum != ref)
  11. {
  12. cout << "违反规则:本条路径的黑色节点的数量跟最左路径不相等" << endl;
  13. return false;
  14. }
  15. return true;
  16. }
  17. //判断当前节点跟它的父亲节点是否都是红的
  18. if (root->_col == RED && root->_parent->_col == RED)
  19. {
  20. cout << "违反规则:出现连续红色节点" << endl;
  21. return false;
  22. }
  23. //如果节点是黑色的,++
  24. if (root->_col == BLACK)
  25. {
  26. ++blackNum;
  27. }
  28. //通过递归,去遍历其它的路径
  29. return Check(root->_left, blackNum, ref)
  30. && Check(root->_right, blackNum, ref);
  31. }
  32. bool IsBalance()
  33. {
  34. //首先看看是不是空树,空树也是红黑树
  35. if (_root == nullptr)
  36. {
  37. return true;
  38. }
  39. //判断根节点是否为黑色,不是黑色就false
  40. if (_root->_col != BLACK)
  41. {
  42. return false;
  43. }
  44. //统计某一条路径的黑色节点的数量
  45. int ref = 0;
  46. //这里选择最左边的路径
  47. Node* left = _root;
  48. while (left)
  49. {
  50. if (left->_col == BLACK)
  51. {
  52. //当节点是黑色,就++一下
  53. ++ref;
  54. }
  55. left = left->_left;
  56. }
  57. //去寻找别的路径上的黑色节点
  58. return Check(_root, 0, ref);
  59. }

红黑树与AVL树的对比

⭐相同点:红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是O(log_2 N)。

⭐不同点:红黑树不追求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对而言降低了插入和旋转的次数。而AVL树是高度平衡的二叉搜索树,旋转的次数比红黑树的要频繁。

所以在经常进行增删的结构中性能比AVL树更优,而且红黑树实现比较简单,所以实际运用中红黑树更多。

也就是因为红黑树在修改操作方面的性能比AVL树好,因此红黑树都用在了C++的STL库(map/set、mutil_map/mutil_set),Java库、Linux内核等等地方。
 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小小林熬夜学编程/article/detail/245990
推荐阅读
相关标签
  

闽ICP备14008679号