当前位置:   article > 正文

[C++][STL源码剖析] 详解AVL树的实现_c++ stl 树

c++ stl 树

目录

1.概念

2.实现

2.1 初始化

2.2 插入

2.2.1 旋转(重点)

左单旋

右单旋

双旋

2.❗ 双旋后,对平衡因子的处理

2.3 判断测试

完整代码:

拓展:删除


1.概念

二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下

因此,两位俄罗斯的数学家G.M.Adelson-Velskii 和E.M.Landis在1962年 发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右 子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。

一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树:

  • 它的左右子树都是AVL树
  • 左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)

高度之差=右子树高度 - 左子树高度

AVL == 高度平衡二叉树搜索树

由于AVL树的自平衡特性,它适用于需要频繁插入和删除操作的场景,尤其是对于需要快速搜索和有序遍历的数据集合。

平衡为什么不是高度差相等,而是高度差不超过 1?

为了涵盖更多的情况,例如为节点个数为 4 如下,高度差 1 也相对平衡了

为什么 满二叉树和 AVL 树是同一个 level?

增删查改:高度次->O(logN)

最后一 h 层有 2^(h-1)个节点

满二叉树 2^h-1=N

AVL 树 2^h-X=N //最后一行还存在缺失

X 范围:[1, 2^(h-1)-1]

满二叉树和 AVL 树 在量级上都是约等于 log N 的

2.实现

2.1 初始化

AVL树的节点定义包括以下几个属性:

  • 值:每个节点存储的值,可以是任意类型,通常是一个关键字或数据。
  • 左子节点指针:指向当前节点的左子节点的指针。左子节点的值应该小于或等于当前节点的值。
  • 右子节点指针:指向当前节点的右子节点的指针。右子节点的值应该大于当前节点的值。
  • 父节点指针:指向当前节点的父节点的指针。根节点的父节点指针为空。(为了便于后面更好的更新设计的)
  • 平衡因子:表示当前节点的左子树高度和右子树高度之差。平衡因子可以为-1、0或1。

下面是一个示例代码来定义一个AVL树的节点结构:

  1. template<class K, class V>
  2. struct AVLTreeNode
  3. {
  4. pair<K, V> _kv;
  5. AVLTreeNode<K, V>* _left;
  6. AVLTreeNode<K, V>* _right;
  7. AVLTreeNode<K, V>* _parent;
  8. int _bf;
  9. AVLTreeNode(const pair<K, V>& kv)
  10. :_kv(kv)
  11. ,_left(nullptr)
  12. ,_right(nullptr)
  13. ,_parent(nullptr)
  14. ,_bf(0) //balance factor
  15. {}
  16. };

2.2 插入

AVL树就是在二叉搜索树的基础上引入了平衡因子,因此AVL树也可以看成是二叉搜索树。那么AVL树的插入过程可以分为两步:

  1. 按照二叉搜索树的方式插入新节点
  2. 调整节点的平衡因子
  1. template<class K, class V>
  2. class AVLTree
  3. {
  4. typedef AVLTreeNode<K, V> Node;
  5. public:
  6. bool Insert(const pair<K, V>& kv)
  7. {//
  8. if (_root == nullptr)
  9. {
  10. _root = new Node(kv);
  11. return true;
  12. }
  13. //搜索找到位置
  14. Node* cur = _root;
  15. while (cur)
  16. {
  17. if (cur->_kv.first < kv.first)
  18. {
  19. parent = cur;
  20. cur = cur->_right;//小于就右移
  21. }
  22. else if (cur->_kv.first > kv.first)
  23. {
  24. parent = cur;
  25. cur = cur->_left;
  26. }
  27. else
  28. {
  29. return false;
  30. }
  31. }//找到一个为空的位置了

生成支点,判断插入

  1. cur = new Node(kv);
  2. if (parent->_kv.first < kv.first)
  3. {
  4. parent->_right = cur;
  5. }
  6. else
  7. {
  8. parent->_left = cur;
  9. }
  10. cur->_parent = parent;//再指回去

插入这部分代码倒是没问题,难的是新节点插入后,AVL树的平衡性可能会遭到破坏,此时就需要更新平衡因子,并检测是否破坏了AVL树,破坏了AVL树就需要旋转调整再次变成AVL树。

如何根据这三种情况来实现插入和对高度的管理?

新增支点:右子树高度++,左子树高度--

插入会对祖先产生影响,平衡因子为 0 了,就再不会对上面的祖先产生影响了,变 0 就平衡了

对以上插入情况,分析可知

是否继续向上更新依旧:子树的高度是否变化

  1. parent->_bf == 0,说明之前parent->_bf是1或者-1,说明之前parent一边高一边低,而这次的插入是把矮的那边填上了,parent所在子树高度不变,不需要往上继续更新
  2. parent->_bf == 1 或者 -1,说明之前parent->_bf为0,两边一样高,现在插入使一边变得更高了,parent所在子树高度变了,继续往上更新
  3. parent->_bf == 2 或者 -2,说明之前parent->_bf是1或者-1,现在插入导致严重不平衡,违反规则,就地处理—>旋转

什么时候结束呢?

_bf==0 或者更新到了根节点的时候

实现平衡因子的更新

  1. // ... 控制平衡
  2. // 更新平衡因子
  3. while (parent)
  4. {
  5. if (cur == parent->_left)
  6. {
  7. parent->_bf--;
  8. }
  9. else // if (cur == parent->_right)
  10. {
  11. parent->_bf++;
  12. }
  13. //判断处理
  14. if (parent->_bf == 0)
  15. {
  16. // 更新结束
  17. break;
  18. }
  19. else if (parent->_bf == 1 || parent->_bf == -1)
  20. {
  21. // 继续往上更新
  22. cur = parent;
  23. parent = parent->_parent;
  24. //回指父指针作用的体现,实现上移了
  25. }
  26. else if (parent->_bf == 2 || parent->_bf == -2)
  27. {
  28. // 子树不平衡了,需要旋转
  29. if (parent->_bf == 2 || cur->bf == 1)
  30. {
  31. RotateL(parent);
  32. }
  33. break;
  34. }
  35. else
  36. {
  37. assert(false);
  38. }
  39. }
  40. return true;
  41. }

接下来我们来看看旋转的实现

2.2.1 旋转(重点)

左单旋

[C++] 详解AVL树左旋的实现~

 

旋转的时候需要注意的问题:

  1. 保持他是搜索树
  2. 变成平衡树且降低这个子树的高度

核心操作:

  1. parent->right=cur->left;
  2. cur->left=parent;

如下情况都会用到左旋:

代码:

  1. void RotateL(Node* parent)
  2. {
  3. // 保存父节点的右子节点
  4. Node* cur = parent->_right;
  5. // 保存右子节点的左子节点
  6. Node* curleft = cur->_left;
  7. // 利用区间性,将子左给父右
  8. parent->_right = curleft;
  9. if (curleft)
  10. {
  11. // 将右子节点的左子节点作为父节点的右子节点
  12. curleft->_parent = parent;
  13. }
  14. // 将父节点作为右子节点的左子节点
  15. cur->_left = parent;
  16. // 保存父节点的父节点
  17. Node* ppnode = parent->_parent;
  18. // 将父节点的父节点指向右子节点
  19. parent->_parent = cur;
  20. // 判断原父节点是否为根节点
  21. if (parent == _root)
  22. {
  23. // 更新根节点为右子节点
  24. _root = cur;
  25. // 将新根节点的父指针置为空
  26. cur->_parent = nullptr;
  27. }
  28. else
  29. {
  30. // 判断原父节点是其父节点的左子节点还是右子节点
  31. if (ppnode->_left == parent)
  32. {
  33. // 更新父节点的左子节点为右子节点
  34. ppnode->_left = cur;
  35. }
  36. else
  37. {
  38. // 更新父节点的右子节点为右子节点
  39. ppnode->_right = cur;
  40. }
  41. // 更新右子节点的父指针为父节点的父节点
  42. cur->_parent = ppnode;
  43. }
  44. // 将父节点和右子节点的平衡因子都设置为0,表示树已经平衡
  45. parent->_bf = cur->_bf = 0;
  46. }
右单旋

代码:

  1. void RotateR(Node* parent)
  2. {
  3. // 获取父节点的左子节点
  4. Node* cur = parent->_left;
  5. // 获取左子节点的右子节点
  6. Node* curright = cur->_right;
  7. // 将左子节点的右子节点作为父节点的左子节点
  8. parent->_left = curright;
  9. if (curright)
  10. {
  11. // 更新左子节点的右子节点的父指针
  12. curright->_parent = parent;
  13. }
  14. // 引入父父节点
  15. Node* ppnode = parent->_parent;
  16. // 将父节点作为左子节点的右子节点
  17. cur->_right = parent;
  18. // 更新父节点的父指针
  19. parent->_parent = cur;
  20. // 判断原父节点是否为根节点
  21. if (ppnode == nullptr)
  22. {
  23. // 更新根节点为左子节点
  24. _root = cur;
  25. // 将新根节点的父指针置为空
  26. cur->_parent = nullptr;
  27. }
  28. else
  29. {
  30. // 判断原父节点是其父节点的左子节点还是右子节点
  31. if (ppnode->_left == parent)
  32. {
  33. // 更新父节点的左子节点为左子节点
  34. ppnode->_left = cur;
  35. }
  36. else
  37. {
  38. // 更新父节点的右子节点为左子节点
  39. ppnode->_right = cur;
  40. }
  41. // 更新左子节点的父指针
  42. cur->_parent = ppnode;
  43. }
  44. // 将父节点和左子节点的平衡因子都设置为0,表示树已经平衡
  45. parent->_bf = cur->_bf = 0;
  46. }
双旋

左右旋转:插入的两种情况,看的是折线情况

直线:单旋 2 1 同号

折线:双旋 2 -1

旋转判断

根据 parent 和 cur 的平衡因子,实现对使用哪种旋转的判断

  1. else if (parent->_bf == 2 || parent->_bf == -2)
  2. {
  3. // 子树不平衡了,需要旋转
  4. if (parent->_bf == 2 && cur->_bf == 1)
  5. {
  6. RotateL(parent);
  7. }
  8. else if (parent->_bf == -2 && cur->_bf == -1)
  9. {
  10. RotateR(parent);
  11. }
  12. //异号
  13. else if (parent->_bf == 2 && cur->_bf == -1)
  14. {
  15. RotateRL(parent);
  16. }
  17. else if (parent->_bf == -2 && cur->_bf == 1)
  18. {
  19. RotateLR(parent);
  20. }
  21. break;
  22. }
  23. else
  24. {
  25. assert(false);
  26. }
  27. }

1.

双旋的结果本质:比 60 小 ,比 30 大的小插入 到 30 下面,找到一个区间中的点

2.❗ 双旋后,对平衡因子的处理

3.h==0 60 本身就是插入的

三种情况,关心 60 的值是-1 0 1

不存在其他奇怪的情况,分别做了 60 的左右

以 RL 为例实现代码:

  1. void RotateRL(Node* parent)
  2. {
  3. Node* cur = parent->_right;
  4. Node* curleft = cur->_left;
  5. int bf = curleft->_bf;
  6. RotateR(parent->_right);
  7. RotateL(parent);
  8. //举例思考填写
  9. if (bf == 0)
  10. {
  11. cur->_bf = 0;
  12. curleft->_bf = 0;
  13. parent->_bf = 0;
  14. }
  15. else if (bf == 1)
  16. {
  17. cur->_bf = 0;
  18. curleft->_bf = 0;
  19. parent->_bf = -1;
  20. }
  21. else if (bf == -1)
  22. {
  23. cur->_bf = 1;
  24. curleft->_bf = 0;
  25. parent->_bf = 0;
  26. }
  27. else
  28. {
  29. assert(false);
  30. }
  31. }

LR 旋转:

平衡因子是根据 curright 初始情况,经过旋转后的图分析分类后得带的

⭕具体而言,先左单旋再右单旋的操作步骤如下:

  • 首先获取节点C的左子节点A(subL)和节点A的右子节点D(subLR);
  • 然后对节点A进行左单旋(RotateL),此时节点C的左子节点应为节点D,节点D的右子节点应为节点A;
  • 最后对节点C进行右单旋(RotateR),此时节点D成为新的子树头节点,节点C成为节点D的右子节点。

最后一部分使用了if语句判断旋转后各个节点的平衡因子,并进行相应的调整,以便使AVL树保持平衡。

  • 如果节点D的平衡因子为1,说明节点D的左子树比右子树高,需要进行右旋操作,这一次旋转中节点C和节点A都向右移动了一位,而节点D的平衡因子变为0,节点A和节点C的平衡因子都变为-1;
  • 如果节点D的平衡因子为-1,说明节点D的右子树比左子树高,需要进行左旋操作,这一次旋转中节点C和节点A都向左移动了一位,而节点D的平衡因子变为0,节点A和节点C的平衡因子都变为1;
  • 如果节点D的平衡因子为0,说明节点D的左右子树高度相等,不需要进行旋转操作,各个节点的平衡因子均设置为0;
  • 如果节点D的平衡因子不是1、-1或者0,则说明AVL树已经失去了平衡,这是一个不合法的状态,应该立即报错退出程序。
  • 经过这两次旋转后,AVL树重新保持了平衡性和有序性。
  1. void RotateLR(Node* parent)
  2. {
  3. Node* cur = parent->_left;
  4. Node* curright = cur->_right;
  5. int bf = curright->_bf;
  6. RotateL(parent->_left);
  7. RotateR(parent);
  8. //解耦合,旋转bf 重新定义
  9. if (bf == 0)
  10. {
  11. parent->_bf = 0;
  12. cur->_bf = 0;
  13. curright->_bf = 0;
  14. }
  15. else if (bf == -1)
  16. {
  17. parent->_bf = 1;
  18. cur->_bf = 0;
  19. curright->_bf = 0;
  20. }
  21. else if (bf == 1)
  22. {
  23. parent->_bf = 0;
  24. cur->_bf = -1;
  25. curright->_bf = 0;
  26. }
  27. }

2.3 判断测试

test 发现不是根,父亲又是空,是为什么呢?

树的结构出问题了,某次旋转出事了

发现错误就是我们的晋级关键时刻

我们可以根据AVL树的性质来测试

一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树:

  1. 它的左右子树都是AVL树
  2. 左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)

求高度这有个对重载函数的巧妙使用:

当传入的节点rootnullptr(空指针)时,说明到达了树的叶子节点的下一层,此时返回高度为0,因为空树的高度定义为0。

  1. int Height()
  2. {
  3. return Height(_root);
  4. }
  5. int Height(Node* root)
  6. {
  7. if (root == nullptr)
  8. return 0;
  9. int leftHeight = Height(root->_left);
  10. int rightHeight = Height(root->_right);
  11. return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
  12. }

对于平衡的测试:

IsBalance(Node* root) 是一个递归函数,其工作流程如下:

  1. 基本情况:如果 rootnullptr,意味着到达了一个空节点,那么认为该子树是平衡的,返回 true
  2. 计算子树高度:计算当前节点的左子树和右子树的高度,分别存储在 leftHightrightHight 变量中。
  3. 检查平衡因子root->_bf 表示当前节点的平衡因子,即右子树的高度减去左子树的高度。如果计算出的实际高度差与存储的平衡因子不匹配,那么输出错误信息并返回 false。这一步是为了验证树的内部数据一致性。
  4. 检查子树平衡性:检查当前节点的左右子树高度差的绝对值是否小于2(即是否平衡)。如果是,则继续递归检查左右子树是否平衡。如果所有的子树都平衡,那么整个树也是平衡的。
  1. bool IsBalance()
  2. {
  3. return IsBalance(_root);
  4. }
  5. bool IsBalance(Node* root)
  6. {
  7. if (root == nullptr)
  8. return true;
  9. int leftHight = Height(root->_left);
  10. int rightHight = Height(root->_right);
  11. if (rightHight - leftHight != root->_bf)
  12. {
  13. cout << "平衡因子异常:" <<root->_kv.first<<"->"<< root->_bf << endl;
  14. return false;
  15. }
  16. return abs(rightHight - leftHight) < 2
  17. && IsBalance(root->_left)
  18. && IsBalance(root->_right);
  19. }
  20. private:
  21. Node* _root = nullptr;
  22. public:
  23. int _rotateCount = 0;
  24. };

手动制作条件断点,一定要注意父亲回指的设定

  1. // 更新父节点的父指针
  2. parent->_parent = cur;

对于这个纰漏的处理,来检验和调试这个问题

测试:

  1. int main()
  2. {
  3. AVLTree<int, int> tree;
  4. // 插入一些节点
  5. tree.Insert({10, 10});
  6. tree.Insert({20, 20});
  7. tree.Insert({30, 30});
  8. tree.Insert({40, 40});
  9. tree.Insert({50, 50});
  10. cout << "树高度: " << tree.Height() << endl;
  11. cout << "树是否平衡: " << (tree.IsBalance() ? "是" : "否") << endl;
  12. // 插入更多节点来触发旋转
  13. tree.Insert({25, 25});
  14. tree.Insert({5, 5});
  15. tree.Insert({15, 15});
  16. cout << "树高度: " << tree.Height() << endl;
  17. cout << "树是否平衡: " << (tree.IsBalance() ? "是" : "否") << endl;
  18. return 0;
  19. }

发现错误:

拼写错误修正:例如 rotateCount 应为 _rotateCount。parent 不要拼写掉 e。

目前还不知道是为什么,重写了一遍,就跑起来了

完整代码:

  1. #pragma once
  2. #include<iostream>
  3. #include<assert.h>
  4. using namespace std;
  5. template<class K, class V>
  6. struct AVLTreeNode
  7. {
  8. pair<K, V> _kv;
  9. AVLTreeNode<K, V>* _left;
  10. AVLTreeNode<K, V>* _right;
  11. AVLTreeNode<K, V>* _parent;
  12. int _bf; // balance factor
  13. AVLTreeNode(const pair<K, V>& kv)
  14. : _kv(kv), _left(nullptr), _right(nullptr), _parent(nullptr), _bf(0)
  15. {}
  16. };
  17. template<class K, class V>
  18. class AVLTree
  19. {
  20. typedef AVLTreeNode<K, V> Node;
  21. public:
  22. bool Insert(const pair<K, V>& kv)
  23. {
  24. if (_root == nullptr)
  25. {
  26. _root = new Node(kv);
  27. return true;
  28. }
  29. Node* parent = nullptr;
  30. Node* cur = _root;
  31. while (cur)
  32. {
  33. if (cur->_kv.first < kv.first)
  34. {
  35. parent = cur;
  36. cur = cur->_right;
  37. }
  38. else if (cur->_kv.first > kv.first)
  39. {
  40. parent = cur;
  41. cur = cur->_left;
  42. }
  43. else
  44. {
  45. return false;
  46. }
  47. }
  48. cur = new Node(kv);
  49. if (parent->_kv.first < kv.first)
  50. {
  51. parent->_right = cur;
  52. }
  53. else
  54. {
  55. parent->_left = cur;
  56. }
  57. cur->_parent = parent;
  58. // Update balance factor
  59. while (parent)
  60. {
  61. if (cur == parent->_left)
  62. {
  63. parent->_bf--;
  64. }
  65. else
  66. {
  67. parent->_bf++;
  68. }
  69. if (parent->_bf == 0)
  70. break;
  71. else if (parent->_bf == 1 || parent->_bf == -1)
  72. {
  73. cur = parent;
  74. parent = parent->_parent;
  75. }
  76. else if (parent->_bf == 2 || parent->_bf == -2)
  77. {
  78. if (parent->_bf == 2 && cur->_bf == 1)
  79. RotateL(parent);
  80. else if (parent->_bf == -2 && cur->_bf == -1)
  81. RotateR(parent);
  82. else if (parent->_bf == 2 && cur->_bf == -1)
  83. RotateRL(parent);
  84. else if (parent->_bf == -2 && cur->_bf == 1)
  85. RotateLR(parent);
  86. break;
  87. }
  88. else
  89. {
  90. assert(false);
  91. }
  92. }
  93. return true;
  94. }
  95. void RotateL(Node* parent)
  96. {
  97. ++_rotateCount;
  98. Node* cur = parent->_right;
  99. Node* curleft = cur->_left;
  100. parent->_right = curleft;
  101. if (curleft)
  102. {
  103. curleft->_parent = parent;
  104. }
  105. cur->_left = parent;
  106. Node* ppnode = parent->_parent;
  107. parent->_parent = cur;
  108. if (parent == _root)
  109. {
  110. _root = cur;
  111. cur->_parent = nullptr;
  112. }
  113. else
  114. {
  115. if (ppnode->_left == parent)
  116. {
  117. ppnode->_left = cur;
  118. }
  119. else
  120. {
  121. ppnode->_right = cur;
  122. }
  123. cur->_parent = ppnode;
  124. }
  125. parent->_bf = cur->_bf = 0;
  126. }
  127. void RotateR(Node* parent)
  128. {
  129. ++_rotateCount;
  130. Node* cur = parent->_left;
  131. Node* curright = cur->_right;
  132. parent->_left = curright;
  133. if (curright)
  134. curright->_parent = parent;
  135. cur->_right = parent;
  136. Node* ppnode = parent->_parent;
  137. parent->_parent = cur;
  138. if (ppnode == nullptr)
  139. {
  140. _root = cur;
  141. cur->_parent = nullptr;
  142. }
  143. else
  144. {
  145. if (ppnode->_left == parent)
  146. {
  147. ppnode->_left = cur;
  148. }
  149. else
  150. {
  151. ppnode->_right = cur;
  152. }
  153. cur->_parent = ppnode;
  154. }
  155. parent->_bf = cur->_bf = 0;
  156. }
  157. void RotateRL(Node* parent)
  158. {
  159. Node* cur = parent->_right;
  160. Node* curleft = cur->_left;
  161. int bf = curleft->_bf;
  162. RotateR(parent->_right);
  163. RotateL(parent);
  164. if (bf == 0)
  165. {
  166. cur->_bf = 0;
  167. curleft->_bf = 0;
  168. parent->_bf = 0;
  169. }
  170. else if (bf == 1)
  171. {
  172. cur->_bf = 0;
  173. curleft->_bf = 0;
  174. parent->_bf = -1;
  175. }
  176. else if (bf == -1)
  177. {
  178. cur->_bf = 1;
  179. curleft->_bf = 0;
  180. parent->_bf = 0;
  181. }
  182. else
  183. {
  184. assert(false);
  185. }
  186. }
  187. void RotateLR(Node* parent)
  188. {
  189. Node* cur = parent->_left;
  190. Node* curright = cur->_right;
  191. int bf = curright->_bf;
  192. RotateL(parent->_left);
  193. RotateR(parent);
  194. if (bf == 0)
  195. {
  196. parent->_bf = 0;
  197. cur->_bf = 0;
  198. curright->_bf = 0;
  199. }
  200. else if (bf == -1)
  201. {
  202. parent->_bf = 1;
  203. cur->_bf = 0;
  204. curright->_bf = 0;
  205. }
  206. else if (bf == 1)
  207. {
  208. parent->_bf = 0;
  209. cur->_bf = -1;
  210. curright->_bf = 0;
  211. }
  212. else
  213. {
  214. assert(false);
  215. }
  216. }
  217. int Height()
  218. {
  219. return Height(_root);
  220. }
  221. int Height(Node* root)
  222. {
  223. if (root == nullptr)
  224. return 0;
  225. int leftHeight = Height(root->_left);
  226. int rightHeight = Height(root->_right);
  227. return max(leftHeight, rightHeight) + 1;
  228. }
  229. bool IsBalance()
  230. {
  231. return IsBalance(_root);
  232. }
  233. bool IsBalance(Node* root)
  234. {
  235. if (root == nullptr)
  236. return true;
  237. int leftHeight = Height(root->_left);
  238. int rightHeight = Height(root->_right);
  239. if (rightHeight - leftHeight != root->_bf)
  240. {
  241. cout << "平衡因子异常:" << root->_kv.first << "->" << root->_bf << endl;
  242. return false;
  243. }
  244. return abs(rightHeight - leftHeight) < 2
  245. && IsBalance(root->_left)
  246. && IsBalance(root->_right);
  247. }
  248. private:
  249. Node* _root = nullptr;
  250. public:
  251. int _rotateCount = 0;
  252. };

拓展:删除

插入到 0,不用更改

删除到 0,还要更改

删除会更加的复杂,平衡因子的更新,旋转等等,将上面的思路总结和拓展一下,大家有兴趣可以看看如下的实现代码:

  1. bool Erase(const pair<T, V>& kv)
  2. {
  3. if (_root == nullptr)
  4. return false;

首先,检查树是否为空。如果树为空,直接返回 false,表示删除失败。

  1. Node* parent = nullptr;
  2. Node* cur = _root;
  3. // 找到要删除的节点
  4. while (cur)
  5. {
  6. if (cur->_kv.first < kv.first)
  7. {
  8. parent = cur;
  9. cur = cur->_right;
  10. }
  11. else if (cur->_kv.first > kv.first)
  12. {
  13. parent = cur;
  14. cur = cur->_left;
  15. }
  16. else
  17. {
  18. break;
  19. }
  20. }
  21. if (cur == nullptr)
  22. return false;

这部分代码用于在树中查找要删除的节点。通过比较当前节点 cur 的键值 cur->_kv.first 与要删除的键值 kv.first,决定向左子树还是右子树继续搜索。最终,cur 将指向要删除的节点,parentcur 的父节点。如果找不到该键值,返回 false

  1. // 处理删除节点的三种情况
  2. if (cur->_left == nullptr)
  3. {
  4. if (parent == nullptr)
  5. {
  6. _root = cur->_right;
  7. if (_root)
  8. _root->_parent = nullptr;
  9. }
  10. else
  11. {
  12. if (cur == parent->_left)
  13. {
  14. parent->_left = cur->_right;
  15. parent->_bf++;
  16. }
  17. else
  18. {
  19. parent->_right = cur->_right;
  20. parent->_bf--;
  21. }
  22. if (cur->_right)
  23. cur->_right->_parent = parent;
  24. }
  25. }
  26. else if (cur->_right == nullptr)
  27. {
  28. if (parent == nullptr)
  29. {
  30. _root = cur->_left;
  31. if (_root)
  32. _root->_parent = nullptr;
  33. }
  34. else
  35. {
  36. if (cur == parent->_left)
  37. {
  38. parent->_left = cur->_left;
  39. parent->_bf++;
  40. }
  41. else
  42. {
  43. parent->_right = cur->_left;
  44. parent->_bf--;
  45. }
  46. if (cur->_left)
  47. cur->_left->_parent = parent;
  48. }
  49. }
  50. else // 左右子树都不为空
  51. {
  52. Node* successorParent = cur;
  53. Node* successor = cur->_right;
  54. while (successor->_left)
  55. {
  56. successorParent = successor;
  57. successor = successor->_left;
  58. }
  59. cur->_kv = successor->_kv;
  60. if (successorParent->_left == successor)
  61. {
  62. successorParent->_left = successor->_right;
  63. successorParent->_bf++;
  64. }
  65. else
  66. {
  67. successorParent->_right = successor->_right;
  68. successorParent->_bf--;
  69. }
  70. if (successor->_right)
  71. successor->_right->_parent = successorParent;
  72. cur = successor;
  73. parent = successorParent;
  74. }
  75. delete cur;

这一部分处理删除节点的三种情况:

  1. 左子树为空:直接用右子树替代删除节点。如果删除节点是根节点,直接更新根节点 _root。否则,更新父节点的左或右子树指针,并调整平衡因子。
  2. 右子树为空:直接用左子树替代删除节点。如果删除节点是根节点,直接更新根节点 _root。否则,更新父节点的左或右子树指针,并调整平衡因子。
  3. 左右子树都不为空:找到右子树中的最小节点(即中序后继节点),用这个节点替代当前节点。然后删除中序后继节点,并调整其父节点的指针和平衡因子。
  1. // 更新平衡因子并处理旋转
  2. bool isLRUpdated = true;
  3. while (parent)
  4. {
  5. if (!isLRUpdated)
  6. {
  7. if (cur == parent->_left)
  8. parent->_bf++;
  9. else
  10. parent->_bf--;
  11. }
  12. isLRUpdated = false;
  13. if (parent->_bf == 1 || parent->_bf == -1)
  14. return true;
  15. else if (parent->_bf == 0)
  16. {
  17. cur = parent;
  18. parent = parent->_parent;
  19. }
  20. else if (parent->_bf == 2 || parent->_bf == -2)
  21. {
  22. Node* higherChild;
  23. int sign;
  24. if (parent->_bf > 0)
  25. {
  26. sign = 1;
  27. higherChild = parent->_right;
  28. }
  29. else
  30. {
  31. sign = -1;
  32. higherChild = parent->_left;
  33. }
  34. if (higherChild->_bf == 0)
  35. {
  36. if (sign > 0)
  37. {
  38. RotateL(parent);
  39. parent->_bf = 1;
  40. higherChild->_bf = -1;
  41. }
  42. else
  43. {
  44. RotateR(parent);
  45. parent->_bf = -1;
  46. higherChild->_bf = 1;
  47. }
  48. return true;
  49. }
  50. else if (higherChild->_bf == sign)
  51. {
  52. if (sign == 1)
  53. RotateL(parent);
  54. else
  55. RotateR(parent);
  56. }
  57. else
  58. {
  59. if (sign == 1)
  60. RotateRL(parent);
  61. else
  62. RotateLR(parent);
  63. }
  64. cur = parent;
  65. parent = cur->_parent;
  66. }
  67. else
  68. {
  69. assert(false);
  70. }
  71. }
  72. return true;
  73. }

这一部分用于在删除节点后更新平衡因子并处理旋转,以保持树的平衡:

  1. 平衡因子为 ±1:子树高度没有变化,直接返回。
  2. 平衡因子为 0:子树高度减少,继续向上更新平衡因子。
  3. 平衡因子为 ±2:子树严重不平衡,需要旋转。根据较高子树的平衡因子选择合适的旋转方式:
    • 如果较高子树的平衡因子为 0,进行单旋转。
    • 如果较高子树的平衡因子与父节点相同,进行单旋转。
    • 如果较高子树的平衡因子与父节点不同,进行双旋转。

通过这些操作,就可以确保树在删除节点后仍然保持平衡啦

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

闽ICP备14008679号