当前位置:   article > 正文

【数据结构】-----红黑树

【数据结构】-----红黑树

目录

前言

一、what is it?

二、How achieve it?

 Ⅰ、结点类

Ⅱ、实现

插入

情况一:叔叔存在且为红色

 情况二:叔叔不存在或者叔叔为黑色

旋转

验证

 ①验证中序遍历

②验证是否满足红黑树的性质

Ⅲ、完整实现代码

三、AVL compare to Red BlackTree


前言

前面介绍了AVL树,它能够解决因单支树导致性能下降的问题,但是AVL树的旋转有些许的麻烦。当然,能解决因单支树导致性能下降不仅有AVL,还有一种数据结构,那就是下面介绍的红黑树。在实际运用比较多的红黑树!

一、what is it?

Ⅰ、概念

红黑树,是一种二叉搜索树,但是每个结点都有颜色,要么红色要么黑色。因此称之为红黑树。它是通过对任何一条从根到叶子的路径上结点着色方式的限制,以确保最长路径<=最短路径的2倍,因此能达到接近平衡的效果。

Ⅱ、性质

  •  根节点是黑色
  • 每个结点不是红色就是黑色
  • 一个红色结点,其两个孩子结点必须是黑色(不能有两个连续的红色结点)
  • 对于每个结点,从该结点到其所有后代结点的路径上,黑色结点的数量必须相同(即每条路径上都存在相同数量的黑色结点)
  • 空结点(NIL)都是黑色的

 问题:红黑树怎么保证结点最长路径<=最短路径*2?

实际上根据性质的第三、四点就可以推出。

因为每条路径上黑色结点数量必须相同那最短路径必然是全黑结点(极端情况下),最长路径必然是一黑一红交替(不一定存在)

因此,就能保证最长路径<=最短路径*2!

注意:这里的路径是从根结点到空结点NIL算做一条。

如下:

注意上图不是一棵红黑树,因为不满足每条路径上的黑色结点数量一致这一性质。从根到6的左子树这条路径上的黑色结点数量为1,其他路径都为2

二、How achieve it?

在给出结点类之前,需要对颜色的存储做一些处理。这里为了代码的可读性,我们对颜色的存储可以采用枚举常量。

  1. //枚举颜色
  2. enum Color
  3. {
  4. RED,
  5. BLACK
  6. };

 Ⅰ、结点类

这里和AVL类似,就是少了个平衡因子,多了个颜色。

  1. template<class K,class V>
  2. struct RBTreeNode
  3. {
  4. RBTreeNode<K, V>* _left;
  5. RBTreeNode<K, V>* _right;
  6. RBTreeNode<K, V>* _parent;
  7. pair<K, V> _kv;
  8. Color _co;
  9. RBTreeNode(const pair<K, V>& kv)
  10. :_left(nullptr)
  11. ,_right(nullptr)
  12. ,_parent(nullptr)
  13. ,_kv(kv)
  14. ,_co(RED)//构造新结点必须是红色,根结点除外,根节点必须黑
  15. {}
  16. };

这里需要注意一个问题:新增结点一定是红色,为何?实际上就是因为第四点性质比第三点性质更加的严格,永远不能动性质四。

  • 若插入黑色结点,那必然违反性质四,即每条路径上的黑色结点数量就不一致了,每条路径都需要调整!
  • 若插入红色结点,那可能违反性质三,若其父亲是黑色,就无需调整;其父亲为红,那就变色,代价较小,所以选择插入红色结点!

注意:根结点一定是黑色的 !

Ⅱ、实现

插入

步骤:

1.首先依旧按照二叉搜索树的规则插入。小的放在左子树,大的放在右子树

2.检测新节点插入后,红黑树的性质是否造到破坏

因为新节点默认是红色的,所以:

  • 新节点的父亲颜色如果是黑色,没有违反性质3,无需调整
  • 新节点的父亲颜色如果是红色,那就需要调整,两种情况讨论

 如果新节点的父亲是红色,说明父亲结点不是根结点,那么就存在祖先,父亲的父亲,即爷爷结点,一定是黑的,不能有两个连续的红结点嘛,所以调整的两种情况主要取决于父亲的兄弟结点,即叔叔结点的颜色。

约定:cur为当前结点(一定红),p为父节点(红),g为爷爷结点(黑),u为叔叔结点

情况一:叔叔存在且为红色

 这种情况的解决方案就是:将父亲和叔叔结点变黑,爷爷结点变红

  • 若爷爷是根结点,调整完成后要变回黑色
  • 若爷爷不是根,是子树,那就需要继续向上调整。调整逻辑也是一样父亲为黑或者空就停止,红就调整。

逻辑代码

  1. //向上调整
  2. cur = g;
  3. parent = cur->_parent;

抽象图

形象图(举例)

可以看到,上述抽象图cur为红色并不是因为其本身就是红色,而是因为cur的子树在调整过程中,将cur结点的颜色有黑色变成红色。

注意:对于这种情况,插在左边右边都是一样的!!变色原理一样,比AVL简单!父亲和叔叔位置互换也一样

代码实现十分简单:

  1. Node* u = g->_right;
  2. //情况一:叔叔存在且为红,叔叔和父亲变红,爷爷g变黑
  3. if (u && u->_co == RED)
  4. {
  5. parent->_co = u->_co = BLACK;
  6. g->_co = RED;
  7. //向上调整
  8. cur = g;
  9. parent = cur->_parent;
  10. }
 情况二:叔叔不存在或者叔叔为黑色

这种情况就需要旋转+变色,旋转的实现和AVL完全类似。变色规则:父亲变黑,爷爷变红温

①不存在

注意:不存在时cur一定是新增,若不是,那cur和p必定有一个黑色结点,这就不满足性质4了!

②存在且为黑

核心逻辑代码:

  1. // g
  2. // p u
  3. // c
  4. //右单旋,p变黑,g变红
  5. if (cur == parent->_left)
  6. {
  7. RotaRTree(g);
  8. parent->_co = BLACK;
  9. g->_co = RED;
  10. }
  11. // g
  12. // p u
  13. // c
  14. //左右双旋,先左再右。cur就变成了p的父亲
  15. else
  16. {
  17. RotaLTree(parent);//左单旋
  18. RotaRTree(g);//右单旋
  19. cur->_co = BLACK;
  20. g->_co = RED;
  21. }

这里旋转代码和AVL树一样,就是将AVL树的平衡因子去掉而已!

插入实现完整代码

  1. bool Insert(const pair<K, V>& kv)
  2. {
  3. if (_root == nullptr)
  4. {
  5. _root = new Node(kv);
  6. _root->_co = BLACK;//根结点一定为黑
  7. return true;
  8. }
  9. //找位置插入
  10. Node* cur = _root;
  11. Node* parent = nullptr;
  12. while (cur)
  13. {
  14. if (kv.first > cur->_kv.first)
  15. {
  16. parent = cur;
  17. cur = cur->_right;
  18. }
  19. else if (kv.first < cur->_kv.first)
  20. {
  21. parent = cur;
  22. cur = cur->_left;
  23. }
  24. else
  25. {
  26. return false;
  27. }
  28. }
  29. cur = new Node(kv);
  30. cur->_co = RED;//新增结点一定为红色
  31. if (kv.first > parent->_kv.first)
  32. {
  33. parent->_right = cur;
  34. }
  35. else
  36. {
  37. parent->_left = cur;
  38. }
  39. cur->_parent = parent;
  40. //插入后,开始调色
  41. //分情况调色
  42. while (parent && parent->_co == RED)//调到黑色或者空就停止
  43. {
  44. Node* g = parent->_parent;
  45. if (parent == g->_left)//父亲在左,叔叔在右
  46. {
  47. Node* u = g->_right;
  48. //情况一:叔叔存在且为红,叔叔和父亲变红,爷爷g变黑
  49. if (u && u->_co == RED)
  50. {
  51. parent->_co = u->_co = BLACK;
  52. g->_co = RED;
  53. //向上调整
  54. cur = g;
  55. parent = cur->_parent;
  56. }
  57. else//叔叔不存在或者存在且为黑
  58. {
  59. // g
  60. // p u
  61. // c
  62. //右单旋,p变黑,g变红
  63. if (cur == parent->_left)
  64. {
  65. RotaRTree(g);
  66. parent->_co = BLACK;
  67. g->_co = RED;
  68. }
  69. // g
  70. // p u
  71. // c
  72. //左右双旋,先左再右。cur就变成了p的父亲
  73. else
  74. {
  75. RotaLTree(parent);//左单旋
  76. RotaRTree(g);//右单旋
  77. cur->_co = BLACK;
  78. g->_co = RED;
  79. }
  80. //不需要再调整,直接结束
  81. break;
  82. }
  83. }
  84. else//父亲在右,叔叔在左
  85. {
  86. Node* u = g->_left;
  87. if (u && u->_co == RED)//u存在且为红
  88. {
  89. parent->_co = u->_co = BLACK;
  90. g->_co = RED;
  91. cur = g;
  92. parent = cur->_parent;
  93. }
  94. else//叔叔不存在或者为黑
  95. {
  96. // g
  97. // u p
  98. // c
  99. //左单旋,p变黑,g变红
  100. if (cur == parent->_right)
  101. {
  102. parent->_co = BLACK;
  103. g->_co = RED;
  104. RotaLTree(g);
  105. }
  106. // g
  107. // u p
  108. // c
  109. //右左双旋,先右旋,在左旋
  110. else
  111. {
  112. RotaRTree(parent);//右
  113. RotaLTree(g);//左
  114. cur->_co = BLACK;
  115. g->_co = RED;
  116. }
  117. break;
  118. }
  119. }
  120. }
  121. _root->_co = BLACK;//始终保持根是黑色
  122. return true;
  123. }

旋转

不管是AVL树还是红黑树,二者需进行旋转形状都是一样,只是规则上的差异而各自进行特殊的处理罢了!

对于红黑树的旋转仅仅只需实现左旋和右旋即可,和AVL代码基本类似,只不过不需要控制平衡因子,情况没有AVL的复杂,十分的简单!这里直接给出实现代码,若有不解可以看看这篇文章AVL旋转

  1. //右旋
  2. void RotaRTree(Node* parent)
  3. {
  4. Node* subL = parent->_left;
  5. Node* subLR = subL->_right;
  6. Node* pparent = parent->_parent;
  7. parent->_left = subLR;
  8. if (subLR)
  9. subLR->_parent = parent;
  10. subL->_right = parent;
  11. parent->_parent = subL;
  12. if (parent == _root)
  13. {
  14. _root = subL;
  15. _root->_parent = nullptr;
  16. }
  17. else
  18. {
  19. if (parent == pparent->_left)
  20. pparent->_left = subL;
  21. else
  22. pparent->_right = subL;
  23. subL->_parent = pparent;
  24. }
  25. }
  26. //左旋
  27. void RotaLTree(Node* parent)
  28. {
  29. Node* subR = parent->_right;
  30. Node* subRL = subR->_left;
  31. Node* pparent = parent->_parent;
  32. parent->_right = subRL;
  33. if (subRL)
  34. subRL->_parent = parent;
  35. subR->_left = parent;
  36. parent->_parent = subR;
  37. if (parent == _root)
  38. {
  39. _root = subR;
  40. _root->_parent = nullptr;
  41. }
  42. else
  43. {
  44. if (parent == pparent->_left)
  45. pparent->_left = subR;
  46. else
  47. pparent->_right = subR;
  48. subR->_parent = pparent;
  49. }
  50. }

验证

 ①验证中序遍历

若中序遍历得到的是一个有序序列,说明为二叉搜索树。

中序遍历和二叉搜索树写法类似

  1. public:
  2. void Inoder()
  3. {
  4. _Inoder(_root);
  5. cout << endl;
  6. }
  7. private:
  8. Node *_root;//私有成员变量
  9. void _Inoder(Node* root)
  10. {
  11. if (root == nullptr)
  12. return;
  13. _Inoder(root->_left);
  14. cout << root->kv.first<< " ";
  15. _Inoder(root->_right);
  16. }
②验证是否满足红黑树的性质

这里的难点是如何验证性质四,即如何判断每条路径上的黑色结点是否一致以及统计黑色结点数量?

统计黑色结点数

思路:实际统计黑色结点数量可以采用dfs,深度优先遍历,设置一个形参变量,一直递归到空结点为止,该形参变量代表的是从根结点到当前结点的路径上的黑色结点数量

判断每条路径上的黑色结点是否一致

思路:可以先去统计任意一条路径上的黑色结点的数量,以该路径黑色结点数量作为基准即可。若统计出有一条路径上黑色结点的数量和基准不 一致,那就出错咯!

实现代码:

  1. bool IsBalance()
  2. {
  3. //根为红就错误
  4. if (_root->_co == RED)
  5. {
  6. cout << "不符合根结点为黑这一规则" << endl;
  7. return false;
  8. }
  9. //先以一条路径为标志位,每条路径上的黑色结点的数和该标志位相比,不一样就不满足规则四
  10. int refnum = 0;//基准位
  11. Node* cur = _root;
  12. while (cur)
  13. {
  14. if (cur->_co == BLACK)
  15. refnum++;
  16. cur = cur->_left;//以最左路径上的黑色结点为基准
  17. }
  18. return _IsBalance(_root, 0, refnum);
  19. }
  20. private:
  21. Node* _root = nullptr;
  22. bool _IsBalance(Node* root, int curblacknum, const int refnum)
  23. {
  24. if (root == nullptr)
  25. {
  26. if (curblacknum != refnum)
  27. {
  28. cout << "存在黑色结点不同的路径" << endl;
  29. return false;
  30. }
  31. return true;
  32. }
  33. if (root->_co == RED && root->_parent->_co == RED)
  34. {
  35. cout << "存在连续两个红色结点" << endl;
  36. return false;
  37. }
  38. //碰到黑色结点就++
  39. if (root->_co == BLACK)
  40. {
  41. ++curblacknum;
  42. }
  43. return _IsBalance(root->_left, curblacknum, refnum) && _IsBalance(root->_right, curblacknum, refnum);
  44. }

Ⅲ、完整实现代码

  1. #include <iostream>
  2. using namespace std;
  3. //枚举颜色
  4. enum Color
  5. {
  6. RED,
  7. BLACK
  8. };
  9. template<class K, class V>
  10. struct RBTreeNode
  11. {
  12. RBTreeNode<K, V>* _left;
  13. RBTreeNode<K, V>* _right;
  14. RBTreeNode<K, V>* _parent;
  15. pair<K, V> _kv;
  16. Color _co;
  17. RBTreeNode(const pair<K, V>& kv)
  18. :_left(nullptr)
  19. , _right(nullptr)
  20. , _parent(nullptr)
  21. , _kv(kv)
  22. , _co(RED)
  23. {}
  24. };
  25. template<class K, class V>
  26. class RBTree
  27. {
  28. typedef RBTreeNode<K, V> Node;
  29. public:
  30. bool Insert(const pair<K, V>& kv)
  31. {
  32. if (_root == nullptr)
  33. {
  34. _root = new Node(kv);
  35. _root->_co = BLACK;//根结点一定为黑
  36. return true;
  37. }
  38. //找位置插入
  39. Node* cur = _root;
  40. Node* parent = nullptr;
  41. while (cur)
  42. {
  43. if (kv.first > cur->_kv.first)
  44. {
  45. parent = cur;
  46. cur = cur->_right;
  47. }
  48. else if (kv.first < cur->_kv.first)
  49. {
  50. parent = cur;
  51. cur = cur->_left;
  52. }
  53. else
  54. {
  55. return false;
  56. }
  57. }
  58. cur = new Node(kv);
  59. cur->_co = RED;//新增结点一定为红色
  60. if (kv.first > parent->_kv.first)
  61. {
  62. parent->_right = cur;
  63. }
  64. else
  65. {
  66. parent->_left = cur;
  67. }
  68. cur->_parent = parent;
  69. //插入后,开始调色
  70. //分情况调色
  71. while (parent && parent->_co == RED)//调到黑色或者空就停止
  72. {
  73. Node* g = parent->_parent;
  74. if (parent == g->_left)//父亲在左,叔叔在右
  75. {
  76. Node* u = g->_right;
  77. //情况一:叔叔存在且为红,叔叔和父亲变红,爷爷g变黑
  78. if (u && u->_co == RED)
  79. {
  80. parent->_co = u->_co = BLACK;
  81. g->_co = RED;
  82. //向上调整
  83. cur = g;
  84. parent = cur->_parent;
  85. }
  86. else//叔叔不存在或者存在且为黑
  87. {
  88. // g
  89. // p u
  90. // c
  91. //右单旋,p变黑,g变红
  92. if (cur == parent->_left)
  93. {
  94. RotaRTree(g);
  95. parent->_co = BLACK;
  96. g->_co = RED;
  97. }
  98. // g
  99. // p u
  100. // c
  101. //左右双旋,先左再右。cur就变成了p的父亲
  102. else
  103. {
  104. RotaLTree(parent);//左单旋
  105. RotaRTree(g);//右单旋
  106. cur->_co = BLACK;
  107. g->_co = RED;
  108. }
  109. //不需要再调整,直接结束
  110. break;
  111. }
  112. }
  113. else//父亲在右,叔叔在左
  114. {
  115. Node* u = g->_left;
  116. if (u && u->_co == RED)//u存在且为红
  117. {
  118. parent->_co = u->_co = BLACK;
  119. g->_co = RED;
  120. cur = g;
  121. parent = cur->_parent;
  122. }
  123. else//叔叔不存在或者为黑
  124. {
  125. // g
  126. // u p
  127. // c
  128. //左单旋,p变黑,g变红
  129. if (cur == parent->_right)
  130. {
  131. parent->_co = BLACK;
  132. g->_co = RED;
  133. RotaLTree(g);
  134. }
  135. // g
  136. // u p
  137. // c
  138. //右左双旋,先右旋,在左旋
  139. else
  140. {
  141. RotaRTree(parent);//右
  142. RotaLTree(g);//左
  143. cur->_co = BLACK;
  144. g->_co = RED;
  145. }
  146. break;
  147. }
  148. }
  149. }
  150. _root->_co = BLACK;//始终保持根是黑色
  151. return true;
  152. }
  153. void InOrder()
  154. {
  155. _InOrder(_root);
  156. cout << endl;
  157. }
  158. bool IsBalance()
  159. {
  160. //根为红就错误
  161. if (_root->_co == RED)
  162. {
  163. cout << "不符合根结点为黑这一规则" << endl;
  164. return false;
  165. }
  166. //先以一条路径为标志位,每条路径上的黑色结点的数和该标志位相比,不一样就不满足规则四
  167. int refnum = 0;//基准位
  168. Node* cur = _root;
  169. while (cur)
  170. {
  171. if (cur->_co == BLACK)
  172. refnum++;
  173. cur = cur->_left;
  174. }
  175. return _IsBalance(_root, 0, refnum);
  176. }
  177. private:
  178. Node* _root = nullptr;
  179. bool _IsBalance(Node* root, int curblacknum, const int refnum)
  180. {
  181. if (root == nullptr)
  182. {
  183. if (curblacknum != refnum)
  184. {
  185. cout << "存在黑色结点不同的路径" << endl;
  186. return false;
  187. }
  188. return true;
  189. }
  190. if (root->_co == RED && root->_parent->_co == RED)
  191. {
  192. cout << "存在连续两个红色结点" << endl;
  193. return false;
  194. }
  195. if (root->_co == BLACK)
  196. {
  197. ++curblacknum;
  198. }
  199. return _IsBalance(root->_left, curblacknum, refnum) && _IsBalance(root->_right, curblacknum, refnum);
  200. }
  201. //右旋
  202. void RotaRTree(Node* parent)
  203. {
  204. Node* subL = parent->_left;
  205. Node* subLR = subL->_right;
  206. Node* pparent = parent->_parent;
  207. parent->_left = subLR;
  208. if (subLR)
  209. subLR->_parent = parent;
  210. subL->_right = parent;
  211. parent->_parent = subL;
  212. if (parent == _root)
  213. {
  214. _root = subL;
  215. _root->_parent = nullptr;
  216. }
  217. else
  218. {
  219. if (parent == pparent->_left)
  220. pparent->_left = subL;
  221. else
  222. pparent->_right = subL;
  223. subL->_parent = pparent;
  224. }
  225. }
  226. //左旋
  227. void RotaLTree(Node* parent)
  228. {
  229. Node* subR = parent->_right;
  230. Node* subRL = subR->_left;
  231. Node* pparent = parent->_parent;
  232. parent->_right = subRL;
  233. if (subRL)
  234. subRL->_parent = parent;
  235. subR->_left = parent;
  236. parent->_parent = subR;
  237. if (parent == _root)
  238. {
  239. _root = subR;
  240. _root->_parent = nullptr;
  241. }
  242. else
  243. {
  244. if (parent == pparent->_left)
  245. pparent->_left = subR;
  246. else
  247. pparent->_right = subR;
  248. subR->_parent = pparent;
  249. }
  250. }
  251. void _InOrder(Node* root)
  252. {
  253. if (root == nullptr)
  254. return;
  255. _InOrder(root->_left);
  256. cout << root->_kv.first << " ";
  257. _InOrder(root->_right);
  258. }
  259. };
  260. void TestRBTree()
  261. {
  262. RBTree<int, int> r;
  263. //int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
  264. int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 ,8, 3, 1, 10, 6, 4, 7, 14, 13 };
  265. for (auto b : a)
  266. {
  267. r.Insert({ b,b });
  268. }
  269. r.InOrder();
  270. if (r.IsBalance())
  271. {
  272. std::cout << "十分的平衡" << std::endl;
  273. }
  274. else
  275. {
  276. std::cout << "歪了" << std::endl;
  277. }
  278. }

三、AVL compare to Red BlackTree

红黑树和AVL树都是高效的平衡二叉树,都能够解决二叉搜索树的单支树的问题,增删改查的时间复杂度都是高度次,即O(logN),但是二者的控制平衡的规则不同

  • 红黑树只需保证最长路径<=最短路径*2即可
  • AVL要严格控制平衡因子,即左右子树的高度差的绝对值不超过1

相对而言,红黑树降低了插入和旋转的次数,所以在经常进行增删的结构中性能比AVL树更优 ,而且红黑树实现起来简单,看代码就能看出来了,所以实际中运用红黑树更多!

红黑树一般多用于:

  • C++STL库---map/set,mutil_map/mutil_set的底层实现(后续有文章)
  • Java库
  • Linux内核
  • 其他一些库

 好了,兄弟们今天的内容就分享到这,如果对你有帮助,欢迎点赞+关注!你的支持永远是我的动力!

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

闽ICP备14008679号