当前位置:   article > 正文

C++ 红黑树 详细讲解_c++红黑树

c++红黑树

目录

一、红黑树的概念

二、红黑树的性质

三、红黑树的实现

1.红黑树节点的定义

2.红黑树节点的插入操作

3.红黑树的验证

4.红黑树的删除

四、红黑树与AVL树的比较

五、完结撒❀


一、红黑树的概念

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

为了后续实现关联式容器简单,红黑树的实现中增加一个头结点,因为根节点必须为黑色,为了
与根节点进行区分,将头结点给成黑色,并且让头结点的 pParent 域指向红黑树的根节点, pLeft
域指向红黑树中最小的节点, _pRight 域指向红黑树中最大的节点,如下:

二、红黑树的性质

1. 每个结点不是红色就是黑色
2. 根节点是黑色的 
3. 如果一个节点是红色的,则它的两个孩子结点是黑色的 
4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点 
5. 每个叶子结点都是黑色的 ( 此处的叶子结点指的是空结点 )
思考:为什么满足上面的性质,红黑树就能保证:其最长路径中节点个数不会超过最短路径节点
个数的两倍?

三、红黑树的实现

1.红黑树节点的定义

  1. // 节点的颜色
  2. enum Color { RED, BLACK };
  3. // 红黑树节点的定义
  4. template<class ValueType>
  5. struct RBTreeNode
  6. {
  7. RBTreeNode(const ValueType& data = ValueType(),Color color = RED)
  8. : _pLeft(nullptr), _pRight(nullptr), _pParent(nullptr)
  9. , _data(data), _color(color)
  10. {}
  11. RBTreeNode<ValueType>* _pLeft;   // 节点的左孩子
  12. RBTreeNode<ValueType>* _pRight;  // 节点的右孩子
  13. RBTreeNode<ValueType>* _pParent; // 节点的双亲(红黑树需要旋转,为了实现简单给
  14. 出该字段)
  15. ValueType _data;            // 节点的值域
  16. Color _color;               // 节点的颜色
  17. };

2.红黑树节点的插入操作

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

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

  1. template<class ValueType>
  2. class RBTree
  3. {
  4. //……
  5. bool Insert(const ValueType& data)
  6. {
  7. PNode& pRoot = GetRoot();
  8. if (nullptr == pRoot)
  9. {
  10. pRoot = new Node(data, BLACK);
  11. // 根的双亲为头节点
  12. pRoot->_pParent = _pHead;
  13. _pHead->_pParent = pRoot;
  14. }
  15. else
  16. {
  17. // 1. 按照二叉搜索的树方式插入新节点(不会的话可以参照我上篇讲AVL树的博客)
  18. // 2. 检测新节点插入后,红黑树的性质是否造到破坏,
  19. //   若满足直接退出,否则对红黑树进行旋转着色处理
  20. }
  21. // 根节点的颜色可能被修改,将其改回黑色
  22. pRoot->_color = BLACK;
  23. _pHead->_pLeft = LeftMost();
  24. _pHead->_pRight = RightMost();
  25. return true;
  26. }
  27. private:
  28. PNode& GetRoot() { return _pHead->_pParent; }
  29. // 获取红黑树中最小节点,即最左侧节点
  30. PNode LeftMost();
  31. // 获取红黑树中最大节点,即最右侧节点
  32. PNode RightMost();
  33. private:
  34. PNode _pHead;
  35. };

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

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

情况一: cur为红,p为红,g为黑,u存在且为红

cur p 均为红,违反了性质三,此处能否将 p 直接改为黑?
不可以,因为违反性质四
解决方式:将 p,u 改为黑, g 改为红,然后把 g 当成 cur ,继续向上调整。

情况二: cur为红,p为红,g为黑,u不存在/u存在且为黑

p g 的左孩子, cur p 的左孩子,则进行右单旋转;相反,
p g 的右孩子, cur p 的右孩子,则进行左单旋转
p g 变色 --p 变黑, g 变红
● 情况三: cur 为红, p 为红, g 为黑, u 不存在 /u 存在且为黑
p g 的左孩子, cur p 的右孩子,则针对 p 做左单旋转;相反,
p g 的右孩子, cur p 的左孩子,则针对 p 做右单旋转
则转换成了情况 2

针对每种情况进行相应的处理即可。
  1. bool Insert(const ValueType& data)
  2. {
  3. //按照搜索二叉树规则进行插入(不会的可以去看我前面所讲的博客)
  4. // ...
  5. // 新节点插入后,如果其双亲节点的颜色为空色,则违反性质3:不能有连在一起的红色结点
  6. while (pParent && RED == pParent->_color)
  7. {
  8. // 注意:grandFather一定存在
  9. // 因为pParent存在,且不是黑色节点,则pParent一定不是根,则其一定有双亲
  10. PNode grandFather = pParent->_pParent;
  11. // 先讨论左侧情况
  12. if (pParent == grandFather->_pLeft)
  13. {
  14. PNode unclue = grandFather->_pRight;
  15. // 叔叔节点存在,且为红
  16. if (unclue && RED == unclue->_color)
  17. {
  18. pParent->_color = BLACK;
  19. unclue->_color = BLACK;
  20. grandFather->_color = RED;
  21. pCur = grandFather;
  22. pParent = pCur->_pParent;
  23. }
  24. else
  25. {
  26. // 叔叔节点不存在,或者叔叔节点存在且为黑
  27. if (pParent->_left == pCur)
  28. {
  29. //单旋
  30. pParent->_col = BLACK;
  31. grandFather->_col = RED;
  32. _RotateRight(grandFather);
  33. }
  34. else
  35. {
  36. //双旋 先左再右
  37. _RotateLeft(pParent);
  38. pCur->_col = BLACK;
  39. grandFather->_col = RED;
  40. _RotateRight(grandFather);
  41. }
  42. break;
  43. }
  44. }
  45. else
  46. {
  47. // 右侧请尝试自己动手完成
  48. }
  49. }
  50. // ...
  51. }

3.红黑树的验证

红黑树的检测分为两步:
        1. 检测其是否满足二叉搜索树 ( 中序遍历是否为有序序列 )
        2. 检测其是否满足红黑树的性质
  1. public:
  2. bool IsRBTree()
  3. {
  4. //根节点一定为黑节点
  5. if (_root->_col == RED)
  6. {
  7. cout << "根节点为红节点" << endl;
  8. return false;
  9. }
  10. int DefNum = 0;
  11. //为了判断每条路径上的黑节点是否相等
  12. //这里可以任意记录一条路径上(这里记录的是最左侧路径)黑节点的个数
  13. //并传值下去进行判断使用
  14. Node* cur = _root;
  15. while (cur)
  16. {
  17. if (cur->_col == BLACK)
  18. {
  19. DefNum++;
  20. }
  21. cur = cur->_left;
  22. }
  23. return _Check(_root,0,DefNum);
  24. }
  25. private:
  26. bool _Check(Node* root,int BlackNum,int DefNum)
  27. {
  28. if (root == nullptr)
  29. {
  30. if (BlackNum != DefNum)
  31. //这里每条路径递归到最后判断黑节点总数是否相等
  32. {
  33. cout << BlackNum << "|" << DefNum << endl;
  34. cout << "存在黑色节点数量不相等的路径" << endl;
  35. return false;
  36. }
  37. return true;
  38. }
  39. //节点为红节点的话其在这一部已经判定一定不是根节点,那么一定有其父节点
  40. if (root->_col == RED && root->_parent->_col == RED)
  41. //因为一个孩子最多有两个孩子
  42. //从上往下判断是否为连续的红节点是比较复杂的
  43. //我们可以从下网上判断,因为一个孩子只有一个父亲
  44. {
  45. cout <<root->_kv.first<<"->存在连续的两个红节点" << endl;
  46. return false;
  47. }
  48. if (root->_col == BLACK)
  49. {
  50. BlackNum++;
  51. }
  52. return _Check(root->_left,BlackNum,DefNum)
  53. && _Check(root->_right,BlackNum,DefNum);
  54. }

4.红黑树的删除

红黑树的删除,有兴趣的同学可参考:《算法导论》或者《 STL 源码剖析》
也可以直接看 红黑树这篇博客进行学习(讲的比我好)!

四、红黑树与AVL树的比较

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

五、完结撒❀

如果以上内容对你有帮助不妨点赞支持一下,以后还会分享更多编程知识,我们一起进步。
最后我想讲的是,据说点赞的都能找到漂亮女朋友❤

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

闽ICP备14008679号