AVL树平衡二叉搜索树详解--实现插入

Myth丶恋晨 2024-04-01 13:26 201阅读 0赞

目录

    • AVL树的概念
    • AVL树的定义
      • Node节点的定义
      • 树的定义
    • AVL树的插入操作
      • 平衡因子更新情况
      • AVL树的旋转(+调平衡因子)
        • 右单旋
        • 左单旋
        • 右左双旋
        • 左右双旋
    • AVL树整体插入代码
    • 验证AVL树
    • AVL树的性能分析
  • 我们之前所学习的二叉搜索树由于可能出现单边树的极端情况,导致效率为O(N)。因此,本文将介绍AVL树即平衡搜索二叉树,将可以有效的避免单边树的情况。

AVL树的概念

当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。

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

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

在这里插入图片描述

如果一棵二叉搜索树是高度平衡的,它就是AVL树。如果它有n个结点,其高度可保持在O(log2N) ,搜索时间复杂度O( log2N)。

AVL树的定义

Node节点的定义

  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)
  15. {
  16. }
  17. };

树的定义

  1. template <class K, class V>
  2. class AVLTree
  3. {
  4. typedef AVLTreeNode<K, V> Node
  5. private:
  6. Node* _root;
  7. private:
  8. //旋转相关函数;
  9. void* RotateL(Node* parent);
  10. void* RotateR(Node* parent);
  11. void* RotateRL(Node* parent);
  12. void* RotateLR(Node* parent);
  13. public:
  14. ;
  15. AVLTree()
  16. :_root(nullptr)
  17. {
  18. }
  19. bool Insert(const pair<K, V>& kv)//插入
  20. {
  21. }
  22. bool Earse();//类似于BST树的删除,只不过需要旋转+要调整平衡因子,我们不做深究;
  23. //中序遍历验证
  24. void _Inorder(Node* root)
  25. {
  26. if (!root) return;
  27. _Inorder(root->_left);
  28. cout << (_root->_kv).first<<" : "<<((_root->_kv)).second << endl;
  29. _Inorder(root->_right);
  30. }
  31. void Inorder() {
  32. _Inorder(_root) };
  33. };

AVL树的插入操作

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

  1. 按照二叉搜索树的方式插入新节点
  2. 调整节点的平衡因子

平衡因子更新情况

在这里插入图片描述

至于上面平衡因子的更新,我们需要借助“旋转”操作来更新:

AVL树的旋转(+调平衡因子)

右单旋

当结点的平衡因子出现异常时,若左子树高度高于右子树高度,那么该结点需要进行右单旋调整。
如图,进行右单旋的条件为:parent的bf为-2且subL的bf为-1

在这里插入图片描述

  1. void RotateR(Node* parent)
  2. {
  3. //改变链接关系;
  4. Node* SubL = parent->_left;
  5. Node* SubLR = SubL->_right;
  6. parent->_left = SubLR;
  7. SubL->_right = parent;
  8. //小心SubLR为空
  9. if (SubLR) SubLR->_parent = parent;
  10. //更新_parent指针
  11. Node* pparent = parent->_parent;
  12. parent->_parent = SubL;
  13. SubL->_parent = pparent;
  14. if (_root == parent) _root = SubL;
  15. else
  16. {
  17. if (pparent->_left == parent)
  18. {
  19. pparent->_left = SubL;
  20. }
  21. else
  22. {
  23. pparent->_right = SubL;
  24. }
  25. }
  26. //更新平衡因子;
  27. SubL->_bf = parent->_bf = 0;
  28. }
左单旋

与右单旋类似的,若左子树高度高于右子树高度,那么该结点需要进行左单旋调整。
如图,进行左单旋的条件为:parent的bf为2且subL的bf为1

在这里插入图片描述

  1. void RotateL(Node* parent)
  2. {
  3. Node* SubR = parent->_right;
  4. Node* SubRL = SubR->_left;
  5. parent->_right = SubRL;
  6. SubR->_left = parent;
  7. if (SubRL) SubRL->_parent = parent;
  8. Node* pparent = parent->_parent;
  9. parent->_parent = SubR;
  10. SubR->_parent = pparent;
  11. if (parent == _root) _root = SubR;
  12. else
  13. {
  14. if (pparent->_left == parent) {
  15. pparent->_left = SubR;
  16. }
  17. else {
  18. pparent->_right = SubR;
  19. }
  20. }
  21. //平衡因子
  22. SubR->_bf = parent->_bf = 0;
  23. }
右左双旋

如图,进行右左双旋的条件为:parent的bf为2且subR的bf为-1.

在这里插入图片描述

  1. //别忘记每次调整完毕需要调整平衡因子!仔细画图分析;
  2. void RotateRL(Node* parent)
  3. {
  4. Node* SubR = parent->_right;
  5. Node* SubRL = SubR->_left;
  6. int bf = SubRL->_bf; //以SubRL这个为依据,分类讨论后续的平衡因子情况;
  7. RotateR(SubR);
  8. RotateL(parent);
  9. if (_bf == 0) {
  10. //SubRL就是新增节点;
  11. SubR->_bf = parent->_bf = SubRL->_bf = 0;
  12. }
  13. else if (_bf == -1) {
  14. //设无关的子树高度为h,SuBRL的左右子树根据分类情况设置为一个1,一个-1,然后画图旋转判断新的bf;
  15. SubR->_bf = 1;
  16. parent = 0;
  17. SubRL = 0;
  18. }
  19. else if (_bf == 1) {
  20. SubR->_bf = 0;
  21. parent = -1;
  22. SubRL = 0;
  23. }
  24. else {
  25. //非法情况;
  26. assert(_bf);
  27. }
  28. }
左右双旋

如图,进行左右双旋的条件为:parent的bf为-2且subL的bf为1.

在这里插入图片描述

  1. void RotateLR(Node* parent)
  2. {
  3. Node* SubL = parent->_left;
  4. Node* SubLR = SubL ->_right;
  5. int bf = SubLR->_bf;
  6. RotateR(SubL);
  7. RotateL(parent);
  8. if (_bf == 0) {
  9. //SubRL就是新增节点;
  10. SubL->_bf = parent->_bf = SubLR->_bf= 0;
  11. }
  12. else if (_bf == 1) {
  13. SubL->_bf = -1;
  14. SubLR->_bf = 0;
  15. parent->_bf = 0;
  16. }
  17. else if (_bf == -1) {
  18. SubL->_bf = 0;
  19. SubLR->_bf = 0;
  20. parent->_bf = 1;
  21. }
  22. else {
  23. //非法情况;
  24. assert(_bf);
  25. }
  26. }

AVL树整体插入代码

  1. bool Insert(const pair<K, V>& kv)//插入
  2. {
  3. //类似于二叉搜索树; 先find位置,再insert
  4. if (_root == nullptr) {
  5. _root = new Node(kv);
  6. return true;
  7. }
  8. Node* cur = _root;
  9. Node* parent = cur;
  10. while (cur)
  11. {
  12. if ((cur->_kv).first > kv.first) {
  13. parent = cur;
  14. cur = cur->_left;
  15. }
  16. else if ((cur->_kv).first < kv.first) {
  17. parent = cur;
  18. cur = cur->_right;
  19. }
  20. else {
  21. //数据冗余 插入错误
  22. return false;
  23. }
  24. }
  25. cur =new Node(kv);
  26. cur->_bf = 0;
  27. cur->_parent = parent;
  28. if (parent->_kv.first >cur->_kv.first) {
  29. parent->_left = cur;
  30. //(parent->_bf)--; 调平衡因子放下面while里轮训来,重要作用!!不然只能调一次,parent的parent就没法变了;
  31. }
  32. else {
  33. parent->_right = cur;
  34. //(parent->_bf)++;
  35. }
  36. //插完了 得整体调整_bf了; 可能连续向上调整,也可能 不 调 整 插入以后父节点bf变0?
  37. //核心部分!
  38. while (parent)
  39. {
  40. if (parent->_left == cur)
  41. parent->_bf--;
  42. else
  43. parent->_bf++;
  44. //不用调整 插完父节点bf=0了; 直接break 插入完毕
  45. if (parent->_bf == 0) break;
  46. //可能需要调整,插完 父节点出现gap了,父节点虽然满足 但是得 向上继续看是否调整; 《一层一层节点向上检索需不需要旋转;》
  47. else if (parent->_bf == 1 || parent->_bf == -1) {
  48. cur = parent;
  49. parent = parent->_parent;
  50. }
  51. //需要调整了;
  52. else if (parent->_bf == 2 || parent->_bf == -2) {
  53. //右单旋
  54. if (parent->_bf == -2 && cur->_bf == -1) {
  55. //这里画图分析条件,因为parent,cur都向上迭代过一次了,所以其实parent == pparent, cur == parent,他们两个就是我们用来判断旋转方法的节点了!
  56. RotateR(parent);
  57. }
  58. //左单旋
  59. else if (parent->_bf == 2 && cur->_bf == 1) {
  60. RotateL(parent);
  61. }
  62. //左 右双旋
  63. else if (parent->_bf == -2 && cur->_bf == 1) {
  64. RotateLR(parent);
  65. }
  66. //右 左双旋
  67. else if (parent->_bf == 2 && cur->_bf == -1) {
  68. RotateRL(parent);
  69. }
  70. break;//调整完必满足AVL性质 break 插入完毕
  71. }
  72. else//若parent的bf为其他情况(不是 0 or +-1 or +-2),说明搜索树的平衡已经破坏,报错
  73. assert(false);
  74. }
  75. return true;
  76. }

验证AVL树

中序遍历即可验证BST树的性质,下面是刷题中用到的自底向上判断avl树;

  1. int flag = 1;//是否是AVL树的标记;
  2. template<class K,class V>
  3. int f(AVLTreeNode<K,V>* cur)
  4. {
  5. if (!cur) return 0;
  6. int a = f(cur->_left) + 1;//自底向上递归
  7. int b = f(cur->_right) + 1;
  8. if (a - b > 1 || b - a > 1) flag = false;
  9. return max(a, b);
  10. }
  11. template<class K, class V>
  12. bool isBalanced(AVLTreeNode< K, V>* root) {
  13. f(root);
  14. return flag;
  15. }
  16. int main()
  17. {
  18. AVLTree <int ,char> t;
  19. t.Insert({
  20. 1,'a' });
  21. t.Insert({
  22. 2,'a' });
  23. t.Insert({
  24. 3,'a' });
  25. t.Insert({
  26. 3,'a' });
  27. t.Insert({
  28. 4,'a' });
  29. t.Insert({
  30. 5,'a' });
  31. t.Insert({
  32. 6,'a' });
  33. t.Insert({
  34. 7,'a' });
  35. t.Inorder();
  36. cout << "是否是AVL树结构?1 or 0 : " << flag << endl;
  37. return 0;
  38. }

在这里插入图片描述

AVL树的性能分析

AVL树是一棵绝对平衡的二叉搜索树,因为每个节点的平衡因子gap不超过1;这样可以保证查询时高效的时间复杂度,即log2(N) ;

但是:如果要对AVL树做一些结构修改的操作,性能非常低下:

比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。

因此:如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(不改变),可以考虑AVL树但一个结构经常修改,就不太适合

后续红黑树针对avl树的优化即将登场!

发表评论

表情:
评论列表 (有 0 条评论,201人围观)

还没有评论,来说两句吧...

相关阅读