当前位置:   article > 正文

数据结构:红黑树

数据结构:红黑树

目录

1.红黑树概念        

 2.红黑树的性质

3. 红黑树实现

3.1 红黑树节点定义

3.2 红黑树的插入操作

3.3 红黑树的验证

3.4 红黑树的删除

 3.5 红黑树与AVL树的比较

4.完整代码


1.红黑树概念        

        红黑树,是一种搜索二叉树,但在每个节点上增加一个存储为表示节点的颜色,可以时Red或者Black。通过对任何一条从根到叶子的路径上各个节点着色方式的限制,红黑树确保没有一条路径路径长出两倍,因而接近平衡。

下面为红黑树的图示:

红黑树

 2.红黑树的性质

        a.每个节点并不是黑色就是红色

        b.根节点是黑色

        c.一个节点为红色,那么它的孩子节点一定都为黑色

        d.对于每个节点,从该节点到其所有后代叶子节点的路径上,均包含相同数目的黑色节点

        e.每个叶子节点都是黑色的(这里的叶子节点指的是空节点)

那为什么满足上述的特征,红黑树就能保证其最长的路径不超过最短路径的二倍呢?

        原因是这样的,假设有一颗全是黑色节点的红黑树,为了满足性质d,这棵树一定为一颗满二叉树。现在插入红色节点,由于性质b,c,每两个黑色节点之间最多只能有一个红色节点,那么,对于任意一条路径,最多只能插入树的深度个红色节点,如下图所示(为了方便起见,下图没有画出空节点):

3. 红黑树实现

3.1 红黑树节点定义

        节点颜色 使用枚举的形式

        以三叉链的形式建立树,节点中存储颜色和一个pair类型的变量

  1. enum Color{BLACK, RED};
  2. template <class K, class V>
  3. struct RBTreeNode
  4. {
  5. //有参构造函数
  6. RBTreeNode*(const pair<K, V>& kv)
  7. :_kv(kv)
  8. ,_col(RED)
  9. ,_left(nullptr)
  10. ,_right(nullptr)
  11. ,_parent(nullptr)
  12. {}
  13. RBTreeNode* _left;
  14. RBTreeNode* _right;
  15. RBTreeNode* _parent;
  16. //存储节点颜色
  17. Color _col;
  18. pair<K, V> _kv;
  19. };

 为什么将节点的默认颜色给为红色?

        在插入节点时,不论是给红色还是黑色,都有可能会违反红黑树的五个性质。若默认颜色给黑色,则每次插入都会违反性质d,所付出的代价是大的,而插入红色节点,也可能不会违反性质,所以我破门选择把每次插入的新节点设为红色。

3.2 红黑树的插入操作

        (1).按照二叉搜索树的规则插入新节点

        (2).检测插入节点后红黑树的性质是否被破坏,若被破坏则进行调整

因为新插入的节点默认颜色是红色的,因此,如果其双亲节点的颜色是黑色的,没有违反红黑树的性质,则不需要调整;但当插入节点的双亲节点为红色的时候,就违反了性质c,树中不能有两个连续的红色节点,此时需要对红黑树进行分情况讨论。

我们这里将红黑树的插入分为三种情况

  • 情况一 :cur为红,parent为红,grandparent为黑,uncle存在且为红  

注意:此时的树可能是一颗完整的树,也可能是一棵子树。

如果grandparent是树的根节点,为了满足性质b,需要将根节点调整为黑色。

如果grandparent不是根节点,若grandparent的根4父节点是红色的话,需要继续向上调整。

这里能否将cur直接变为黑色呢?

        答案肯定是不行的,若将cur直接变为黑色,则不满足性质d了。

        解决方案:parent、uncle变黑,grandparent变红,然后把grandparent当cur继续向上调整

  • 情况二:cur为红,parent为红,grandparent为黑,uncle不存在/为黑(三个节点为直线)

        (1) uncle不存在时  解决方案:向把13进行右旋操作,parent变黑,grandparent变红

         uncle不存在时cur一定为新增的节点,若cur不为新增的节点,那么它的孩子中一定存在黑色的节点,而这使得以grandparent为根的 树/子树不满足性质d。所以cur一定为新增的节点。

        (2) uncle存在且为黑色时 解决方案:把13右旋,parent变黑,grandparent变红

         如果uncle存在且为黑,那么cur一定不是新增的节点,cur原来是黑色,是由于cur的子树调节而变红的。

        parent为grandparent的左孩子,cur为parent的左孩子,先右旋再变色;

        parent为grandparent的右孩子,cur为parent的右孩子,先左旋再变色。

        这两种情况我只画了第一种,第二种也一个道理,可以自行尝试。

  • 情况三:cur为红,parent为红,grandparent为黑,uncle不存在/为黑(三个节点为折线)

        (1) uncle不存在 解决方案:8左旋,13右旋,parent变黑,grandparent变红

       (2) u存在且为黑色 解决方案:8左旋,13右旋,parent变黑,grandparent变红

        parent为grandparent的左孩子,cur为parent的右孩子,对parent进行左旋

        parent为grandparent的右孩子,cur为parent的左孩子,对parent进行右旋

        此时,转变为了情况二。

        这两种情况只花了第一种,第二种情况可自己尝试。

那么,上述就为红黑树插入的三种情况了!下面实现红黑树插入的代码。

  1. template <class K, class V>
  2. typedef RBtreeNode<K, V> Node;
  3. bool Insert(const pair<K, V>& kv)
  4. {
  5. //树为空,则直接插入
  6. if(_root == nullptr)
  7. {
  8. _root = new Node(kv);
  9. //根一定为黑色
  10. _root->col = BLACK;
  11. return true;
  12. }
  13. //按照 二叉树的搜索规则寻找插入位置
  14. Node* parent = nullptr;
  15. Node* cur = _root;
  16. //找到要插入的位置
  17. while (cur)
  18. {
  19. if (cur->_kv.first < kv.first)
  20. {
  21. parent = cur;
  22. cur = cur->_right;
  23. }
  24. else if (cur->_kv.first > kv.first)
  25. {
  26. parent = cur;
  27. cur = cur->_left;
  28. }
  29. else
  30. return false;
  31. }
  32. cur = new Node(kv);
  33. if (parent->_kv.first < kv.first)
  34. parent->_right = cur;
  35. else
  36. parent->_left = cur;
  37. cur->_parent = parent;
  38. //新增节点给红色
  39. cur->_col = RED;
  40. //可能为连续调节 parent为黑色时不用处理
  41. while (parent && parent->_col == RED)
  42. {
  43. //红黑树的调节关键看叔叔节点(uncle节点)
  44. Node* grandfather = parent->_parent;
  45. if (grandfather->_left == parent)
  46. {
  47. Node* uncle = grandfather->_right;
  48. //情况一
  49. //uncle存在,且为红色
  50. if(uncle && uncle->_col == RED)
  51. {
  52. parent->_col = BLACK;
  53. uncle->_col = BLACK;
  54. grandfather->_col = RED;
  55. //继续向上处理
  56. cur = grandfather;
  57. parent = cur->_parent;
  58. }
  59. //情况二、三
  60. //uncle不存在或者为黑色
  61. else
  62. {
  63. if(cur == parent->_right)
  64. {
  65. //parent左旋
  66. RotateL(parent);
  67. //交换parent和cur 转换为情况二
  68. swap(parent, cur);
  69. }
  70. //情况二
  71. //grandfather右旋,然后节点变色
  72. RotateR(grandfather);
  73. grandfather->_col = RED;
  74. parent->_col = BLACK;
  75. break;
  76. }
  77. }
  78. //方向相反的情况
  79. else
  80. {
  81. Node* uncle = grandfather->_left;
  82. //情况一
  83. //uncle 存在 且为红色
  84. if (uncle && uncle->_col == RED)
  85. {
  86. parent->_col = BLACK;
  87. uncle->_col = BLACK;
  88. grandfather->_col = RED;
  89. //继续向上处理
  90. cur = grandfather;
  91. parent = grandfather->_parent;
  92. }
  93. //第二 第三种情况 uncle 不存在或者为黑色 需要旋转
  94. else
  95. {
  96. //情况三 uncle为存在且为黑色 折线 需要双旋
  97. if (cur == parent->_left)
  98. {
  99. //先把parent右旋
  100. RotateR(parent);
  101. //把两个节点指针只想交换 成为第二种情况
  102. swap(parent, cur);
  103. }
  104. //第二种情况
  105. //将grandfather左旋
  106. RotateL(grandfather);
  107. grandfather->_col = RED;
  108. //因为parent与cur交换了 所以这里是将parent的颜色改变
  109. parent->_col = BLACK;
  110. break;
  111. }
  112. }
  113. }
  114. _root->_col = BLACK;
  115. return true;
  116. }

下面为左旋还有右旋的代码

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

3.3 红黑树的验证

红黑树的检测分为两步:

        1. 检测其是否满足二叉搜索树(中序遍历是否为有序序列)

        2. 检测其是否满足红黑树的性质 

  1. //验证子函数
  2. bool _IsValidRBTree(Node* pRoot, size_t k, size_t countBlack)
  3. {
  4. //当pNode走到nullptr时,判断此条路径的黑色节点个数与countBlack是否相同
  5. if(pRoot == bullptr)
  6. {
  7. if(k != countBlack)
  8. {
  9. cout<<"不满足性质d"<<endl;
  10. return false;
  11. }
  12. return true;
  13. }
  14. //统计黑色节点个数
  15. if(pRoot->_col == BLACK)
  16. k++;
  17. //检测当前节点与双亲是否都为红色
  18. if(pRoot->_parent && pRoot->_col == RED && pRoot->_parent->_col == RED)
  19. {
  20. cout<<"违反了性质c"<<endl;
  21. return false;
  22. }
  23. //左右子树都要满足红黑树性质
  24. return _IsValidRBTree(pRoot->_left, k, countBlack) && _ISValidRBTree(pRoot->_right, k, countBlack);
  25. }
  26. bool IsValidRBTree()
  27. {
  28. //获取根节点
  29. Node* pRoot = _root;
  30. //空树为红黑树
  31. if(pRoot == nullptr)
  32. {
  33. return true;
  34. }
  35. //检查根节点是否满足情况
  36. if(pRoot->_col != BLACK)
  37. {
  38. cout<<"不满足性质b"<<endl;
  39. returnv false;
  40. }
  41. //获取任意路径中的黑色节点个数
  42. size_t countBlack = 0;
  43. Node* cur = pRoot;
  44. while(cur)
  45. {
  46. if(cur->_col == BLACK)
  47. countBlack++;
  48. cur = cur->_left;
  49. }
  50. //检测是否满足红黑树的性质,k用来记录路径中黑色节点的个数
  51. return _IsValidRBTree(pRoot, k, countBlack);
  52. }

3.4 红黑树的删除

本文不对红黑树的删除进行详细的讲解,其与二叉搜索树的删除规则相同。

删除后,再判断是否满足红黑树的性质。

删除红色节点时,是不会出现问题的,按照二叉搜索树的规则删除即可。

删除黑色节点时,如果能找到红色节点代替,则用红节点代替,在变为黑色节点,若找不到,则要进行旋转。感兴趣的可以自己看下面的两个链接:

https://blog.csdn.net/chenhuajie123/article/details/11951777

https://www.cnblogs.com/fornever/archive/2011/12/02/2270692.html

 3.5 红黑树与AVL树的比较

        红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是O( ),红黑树不追求绝对平衡,其 只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数,所以在经常进行增删的结构 中性能比AVL树更优,而且红黑树实现比较简单,所以实际运用中红黑树更多。

STL容器map和set底层就是用红黑树实现的,后续会出模拟实现map和set的文章。

4.完整代码

  1. #include<iostream>
  2. using namespace std;
  3. #include<utility>
  4. enum Colour
  5. {
  6. BLACK,
  7. RED,
  8. };
  9. template<class K,class V>
  10. class RBTreeNode
  11. {
  12. public:
  13. RBTreeNode<K, V>(const pair<K,V>& kv)
  14. :_kv(kv)
  15. ,_left(nullptr)
  16. ,_right(nullptr)
  17. ,_parent(nullptr)
  18. ,_col(RED)
  19. {}
  20. RBTreeNode<K, V>* _left;
  21. RBTreeNode<K, V>* _parent;
  22. RBTreeNode<K, V>* _right;
  23. pair<K, V> _kv;
  24. Colour _col;
  25. };
  26. template<class K,class V>
  27. class RBTree
  28. {
  29. typedef RBTreeNode<K, V> Node;
  30. public:
  31. bool Insert(const pair<K, V>& kv)
  32. {
  33. //...
  34. }
  35. //左旋
  36. void RotateL(Node* parent)
  37. {
  38. //...
  39. }
  40. //右旋
  41. void RotateR(Node* parent)
  42. {
  43. //...
  44. }
  45. void _InOrder(Node* root)
  46. {
  47. if (root == nullptr)
  48. return;
  49. _InOrder(root->_left);
  50. cout << root->_kv.first << ":" << root->_kv.second << endl;
  51. _InOrder(root->_right);
  52. }
  53. //中序遍历
  54. void InOrder()
  55. {
  56. _InOrder(_root);
  57. }
  58. //获取根节点
  59. Node* GetRoot()
  60. {
  61. return _root;
  62. }
  63. //检测是否满足红黑树性质
  64. bool IsValidRBTree()
  65. {
  66. //...
  67. }
  68. bool _IsValidRBTree(Node* pRoot, size_t k, const size_t blackCount)
  69. {
  70. //...
  71. }
  72. //删除
  73. private:
  74. Node* _root = nullptr;
  75. };
  76. void testRBTree()
  77. {
  78. int a[] = { 4,2,6,1,3,5,15,7,16,14 };
  79. RBTree<int, int> t;
  80. for (auto e : a)
  81. {
  82. t.Insert(make_pair(e, e));
  83. }
  84. t.InOrder();
  85. cout << t.IsValidRBTree() << endl;
  86. }
  87. int main()
  88. {
  89. testRBTree();
  90. return 0;
  91. }

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

闽ICP备14008679号