• 【C++ 学习 ㉓】- 详解红黑树


    目录

    一、红黑树的概念和性质

    二、红黑树的两个结论

    三、红黑树节点的定义

    四、红黑树的插入

    五、红黑树的实现

    5.1 - RBT.h

    5.2 - test.cpp

    六、红黑树和 AVL 树的比较


     


    一、红黑树的概念和性质

    红黑树(red-black tree)是这样的一棵二叉搜索树:树中的每个节点的颜色不是红色就是黑色。可以把一棵红黑树视为一棵扩充二叉树,用外部节点表示空指针。其特性描述如下

    1. 根节点和所有外部节点的颜色是黑色

    2. 从根节点到外部节点的途中没有连续两个节点的颜色是红色

      特性 2 意味着如果一个节点的颜色是红色,则它的左、右孩子的颜色必然是黑色

    3. 对任一节点,所有从该节点到外部节点的路径上都有相同数目的黑色节点


    二、红黑树的两个结论

    从红黑树中任一节点 x 出发(不包括节点 x),到达任一个外部节点的路径上的黑节点个数叫做节点 x 的黑高度,亦称为节点的阶,记作 bh(x)。红黑树的黑高度定义为其根节点的黑高度。

    下图所示的二叉搜索树就是一棵红黑树,节点上边的数字为该节点的黑高度。

    1. 结论一:对任一节点 x,假设它的黑高度为 r,那么从节点 x 到任一外部节点的路径长度为 r ~ 2r

      注意:从节点 x 到任一外部节点的路径长度为该路径上的指针的个数

    2. 结论二:假设 h 是一棵红黑树的高度(不包括外部节点),n 是红黑树中内部节点的个数, r 是红黑树的黑高度,则以下关系式成立

      (1) ​h \le 2r

      (2) n \ge 2^r - 1

      (3) h \le 2log_2^{(n+1)}

      证明

      (1) 由结论一即可证明。上图所示的红黑树的高度(不计外部节点)为 2r = 4

      (2) 因为红黑树的黑高度为 r,即从红黑树的第 1 层到第 r 层没有外部节点,所以内部节点的总数至少为 2^r - 1。在上图所示的红黑树中,红黑树的黑高度为 r = 2,第 1 层和第 2 层总共有 2^r - 1 = 3 个内部节点,而在第 3 层和第 4 层还有 7 个节点,则有 ​n \ge 2^r - 1

      (3) 由 (2) 可得 r \le log_2^{(n+1)},结合 (1),有 ​h \le 2log_2^{(n+1)}

      由此也可以知道红黑树的搜索、插入、删除操作的时间复杂度为 O(log_2^n​)


    三、红黑树节点的定义

    1. enum Color
    2. {
    3. RED,
    4. BLACK
    5. };
    6. template<class K, class V>
    7. struct RBTNode
    8. {
    9. RBTNode* _left;
    10. RBTNode* _right;
    11. RBTNode* _parent;
    12. std::pair _kv;
    13. Color _clr;
    14. RBTNode(const std::pair& kv = std::pair(), Color clr = RED)
    15. : _left(nullptr), _right(nullptr), _parent(nullptr), _kv(kv), _clr(clr)
    16. { }
    17. };


    四、红黑树的插入

    如果插入前树非空,若新节点被染成黑色,将违反红黑树的特性 3,即所有从根节点到外部节点的路径上的黑色节点个数变得不相等了,因此如果插入前树非空,新插入的节点应该被然成红色

    如果新插入的节点的父节点是黑色节点,则没有违反红黑树的任何特性,不需要做调整,即插入结束;如果新插入的节点的父节点是红色节点,则违反了红黑树的特性 2,即出现了连续两个红色节点,需要做平衡调整

    *cur 为当前节点,它和它的父结点 *parent 都是红色节点,它的祖父节点 *grandparent 是黑色节点,即出现了连续两个红色节点的情形,此时通过考查其叔叔节点 *uncle 做相应的平衡调整

    1. 如果 *uncle 是红色节点,则将 *parent*uncle 改为黑色节点,将 *grandparent 改为红色节点。例如

      如果 *grandparent 是根节点,则将其再改为黑色节点,调整完成

      如果 *grandparent 是子树的根,则其父节点也可能是红色节点,所以还需要继续往上调整

    2. 如果 *uncle 不存在或者是黑色节点,此时又有以下 4 种情况

      (1) *parent*grandparent 的左孩子,*cur*parent 的左孩子。在这种情况下,只要做一次右单旋,再将 *parent 改为黑色节点,*grandparent 改为红色节点,就可恢复红黑树的特性,并结束平衡调整

      (2) *parent*grandparent 的左孩子,*cur*parent 的右孩子。在这种情况下,只要做一次先左后右的双旋转,再将 *cur 改为黑色节点,*grandparent 改为红色节点,就可恢复红黑树的特性,并结束平衡调整

      (3) *parent*grandparent 的右孩子,*cur*parent 的右孩子

      (4) *parent*grandparent 的右孩子,*cur*parent 的左孩子

      情况 (2)、(3) 和情况 (1)、(2) 是镜像的

     


    五、红黑树的实现

    5.1 - RBT.h

    1. #pragma once
    2. #include
    3. enum Color
    4. {
    5. RED,
    6. BLACK
    7. };
    8. template<class K, class V>
    9. struct RBTNode
    10. {
    11. RBTNode* _left;
    12. RBTNode* _right;
    13. RBTNode* _parent;
    14. std::pair _kv;
    15. Color _clr;
    16. RBTNode(const std::pair& kv = std::pair(), Color clr = RED)
    17. : _left(nullptr), _right(nullptr), _parent(nullptr), _kv(kv), _clr(clr)
    18. { }
    19. };
    20. template<class K, class V>
    21. class RBT
    22. {
    23. typedef RBTNode Node;
    24. public:
    25. RBT() : _root(nullptr) { }
    26. ~RBT()
    27. {
    28. Destroy(_root);
    29. }
    30. bool isValidRBT()
    31. {
    32. if (_root == nullptr)
    33. return true;
    34. if (_root->_clr == RED)
    35. return false;
    36. // 获取从根节点到任一外部节点的路径上的黑色节点的个数
    37. size_t blackCount = 0;
    38. Node* cur = _root;
    39. while (cur)
    40. {
    41. if (cur->_clr == BLACK)
    42. ++blackCount;
    43. cur = cur->_left;
    44. }
    45. size_t k = 0;  // k 用来路径种的黑色节点个数
    46. return _isValidRBT(_root, blackCount, k);
    47. }
    48. bool insert(const std::pair& kv)
    49. {
    50. if (_root == nullptr)
    51. {
    52. _root = new Node(kv, BLACK);
    53. return true;
    54. }
    55. Node* parent = nullptr;
    56. Node* cur = _root;
    57. while (cur)
    58. {
    59. parent = cur;
    60. if (kv.first < cur->_kv.first)
    61. cur = cur->_left;
    62. else if (kv.first > cur->_kv.first)
    63. cur = cur->_right;
    64. else
    65. return false;
    66. }
    67. // 如果插入前树非空,新插入的节点应该是红色节点
    68. cur = new Node(kv, RED);
    69. cur->_parent = parent;
    70. // 出现连续两个红色节点的情形
    71. while (parent && parent->_clr == RED)
    72. {
    73. Node* grandparent = parent->_parent;
    74. Node* uncle;
    75. if (grandparent->_left == parent)
    76. uncle = grandparent->_right;
    77. else
    78. uncle = grandparent->_left;
    79. // 如果 *uncle 是红色节点
    80. if (uncle && uncle->_clr == RED)
    81. {
    82. parent->_clr = uncle->_clr = BLACK;
    83. grandparent->_clr = RED;
    84. cur = grandparent;
    85. parent = cur->_parent;
    86. }
    87. else  // 如果 *uncle 不存在或者是黑色节点
    88. {
    89. if (grandparent->_left == parent && parent->_left == cur)
    90. {
    91. LL(grandparent);
    92. parent->_clr = BLACK;
    93. grandparent->_clr = RED;
    94. }
    95. else if (grandparent->_right == parent && parent->_right == cur)
    96. {
    97. RR(grandparent);
    98. parent->_clr = BLACK;
    99. grandparent->_clr = RED;
    100. }
    101. else if (grandparent->_left == parent && parent->_right == cur)
    102. {
    103. LR(grandparent);
    104. cur->_clr = BLACK;
    105. grandparent->_clr = RED;
    106. }
    107. else if (grandparent->_right == parent && parent->_left == cur)
    108. {
    109. RL(grandparent);
    110. cur->_clr = BLACK;
    111. grandparent->_clr = RED;
    112. }
    113. break;
    114. }
    115. }
    116. _root->_clr = BLACK;
    117. return true;
    118. }
    119. private:
    120. void Destroy(Node*& root)
    121. {
    122. // 【注意:root 为 _root 或者某个节点的左或右指针的引用】
    123. if (root == nullptr)
    124. return;
    125. Destroy(root->_left);
    126. Destroy(root->_right);
    127. delete root;
    128. root = nullptr;
    129. }
    130.    
    131.    bool _isValidRBT(Node* root, size_t blackCount, size_t k)
    132. {
    133. if (root == nullptr)
    134. {
    135. if (blackCount == k)
    136. return true;
    137. else
    138. return false;  // 违反了特性 3
    139. }
    140. if (root->_clr == BLACK)
    141. {
    142. ++k;
    143. }
    144. else
    145. {
    146. if (root->_parent && root->_parent->_clr == RED)
    147. return false;  // 违反了特性 2
    148. }
    149. return _isValidRBT(root->_left, blackCount, k)
    150. && _isValidRBT(root->_right, blackCount, k);
    151. }
    152. void LL(Node* parent)
    153. {
    154. Node* cur = parent->_left;
    155. Node* curRight = cur->_right;
    156. parent->_left = curRight;
    157. if (curRight)
    158. curRight->_parent = parent;
    159. cur->_right = parent;
    160. Node* tmp = parent->_parent;
    161. parent->_parent = cur;
    162. cur->_parent = tmp;
    163. if (tmp == nullptr)
    164. {
    165. _root = cur;
    166. }
    167. else
    168. {
    169. if (tmp->_left == parent)
    170. tmp->_left = cur;
    171. else
    172. tmp->_right = cur;
    173. }
    174. }
    175. void RR(Node* parent)
    176. {
    177. Node* cur = parent->_right;
    178. Node* curLeft = cur->_left;
    179. parent->_right = curLeft;
    180. if (curLeft)
    181. curLeft->_parent = parent;
    182. cur->_left = parent;
    183. Node* tmp = parent->_parent;
    184. parent->_parent = cur;
    185. cur->_parent = tmp;
    186. if (tmp == nullptr)
    187. {
    188. _root = cur;
    189. }
    190. else
    191. {
    192. if (tmp->_left == parent)
    193. tmp->_left = cur;
    194. else
    195. tmp->_right = cur;
    196. }
    197. }
    198. void LR(Node* parent)
    199. {
    200. Node* cur = parent->_left;
    201. RR(cur);
    202. LL(parent);
    203. }
    204. void RL(Node* parent)
    205. {
    206. Node* cur = parent->_right;
    207. LL(cur);
    208. RR(parent);
    209. }
    210. private:
    211. Node* _root;
    212. };

    5.2 - test.cpp

    1. #include "RBT.h"
    2. #include
    3. #include
    4. #include
    5. using namespace std;
    6. void TestRBT()
    7. {
    8. srand((unsigned int)time(0));
    9. size_t N = 10000;
    10. vector<int> v;
    11. for (size_t i = 0; i < N; ++i)
    12. {
    13. v.push_back(rand());
    14. }
    15. RBT<int, int> t;
    16. for (const auto& e : v)
    17. {
    18. t.insert(make_pair(e, e));
    19. if (!t.isValidRBT())
    20. cout << "插入操作有误!" << endl;
    21. }
    22. cout << "插入完成~" << endl;
    23. }
    24. int main()
    25. {
    26. TestRBT();
    27. return 0;
    28. }


    六、红黑树和 AVL 树的比较

    红黑树和 AVL 树的增删查改的效率都很高,时间复杂度为 O(​),但红黑树不追求极致的平衡,其只需保证最长路径不超过最短路径的 2 倍,相对而言,降低了平衡旋转的次数,所以在经常进行增删的结构中,红黑树的性能比 AVL 树更好,并且红黑树的实现更简单,所以在实际应用中,使用红黑树的也更多

  • 相关阅读:
    Zookeeper系列——3Zookeeper源码分析之Session管理及请求处理
    《原CSharp》第二回 巧习得元素分类 子不知怀璧其罪
    SD卡使用记录
    Docker部署单节点Kafka
    Entity FrameWork Core教程,从基础应用到原理实战
    如何改变胆小怕事的性格?
    大规模爬虫系统面临的主要挑战及解决思路
    java编程之多线程实战指南(设计模式篇),从基础到避坑
    SpringBoot SpringBoot 开发实用篇 3 测试 3.3 测试类中启动web 环境
    Java 栈和队列的基本使用
  • 原文地址:https://blog.csdn.net/melonyzzZ/article/details/133213928