• 【C++AVL树】4种旋转详讲


    目录

    引子:AVL树是因为什么出现的?

    1.AVl树的的特性

    2.AVl树的框架

    3.AVL树的插入 

             3.1四种旋转(左单旋、右单旋、左右双旋、右左双旋)

            3.1.1左单旋

            3.1.2右单旋

             3.1.3左右双旋

             3.1.4右左双旋 

     总结


    引子:AVL树是因为什么出现的?

    • 二叉搜索树可以缩短查找的效率,如果数据有序接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下时间复杂度:O(N)

     两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年 发明了一种解决上述问题的方法:当向二叉搜索树插入新结点后,如果能保证每个结点左右子树高度之差的绝对值不超过1(对树中的结点进行调整),即为AVl树以他们的名字缩写命名也可以叫高度二叉搜索树

    1.AVl树的的特性

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

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

    如果AVl树有n个结点,其高度可保持在O(logN)搜索时间复杂度O(logN),为什么?

    答:左右子树高度之差的绝对值不超过1,那么只有最后一层会差一部分的节点;

    2.AVl树的框架

    1. template<class K, class V>
    2. struct AVLtreeNode
    3. {
    4. //节点构造函数
    5. AVLtreeNode(const pair& kv)
    6. :_left(nullptr)
    7. ,_right(nullptr)
    8. ,_parent(nullptr)
    9. ,_bf(0)
    10. ,_kv(kv)
    11. {}
    12. //节点的成员
    13. //三叉链
    14. AVLtreeNode* _left;
    15. AVLtreeNode* _right;
    16. AVLtreeNode* _parent;
    17. int _bf;//平衡因子
    18. //数据使用库里面的pair类存储的kv
    19. pair _kv;
    20. };
    21. template<class K,class V>
    22. class AVLtree
    23. {
    24. typedef AVLtreeNode Node;
    25. public:
    26. //构造函数
    27. AVLtree()
    28. :_root(nullptr)
    29. {}
    30. //四种旋转
    31. void RotateL(Node* parent)
    32. void RotateR(Node* parent)
    33. void RotateLR(Node* parent)
    34. void RotateRL(Node* parent)
    35. //插入
    36. bool Insert(const pair& kv)
    37. //寻找
    38. Node* Find(const K& kv)
    39. private:
    40. Node* _root;
    41. };

    三叉链是什么?

    3.AVL树的插入 

    1. bool Insert(const pair& kv)
    2. {
    3. if (_root == nullptr)
    4. {
    5. _root = new Node(kv);
    6. return true;
    7. }
    8. Node* parent = _root, *cur = _root;
    9. while (cur)
    10. {
    11. //找nulptr,如果已经有这个key了,二叉搜索树的特性不支持冗余,所以返回失败
    12. if (cur->_kv.first > kv.first)
    13. {
    14. parent = cur;
    15. cur = cur->_left;
    16. }
    17. else if (cur->_kv.first
    18. {
    19. parent = cur;
    20. cur = cur->_right;
    21. }
    22. else
    23. {
    24. return false;
    25. }
    26. }
    27. //
    28. cur = new Node(kv);
    29. //判断孩子在父亲的左边还是右边
    30. if (cur->_kv.first > parent->_kv.first)
    31. {
    32. parent->_right = cur;
    33. cur->_parent = parent;
    34. }
    35. else
    36. {
    37. parent->_left = cur;
    38. cur->_parent = parent;
    39. }
    40. while (parent)
    41. {
    42. //影响一条路径所有的祖先
    43. if (parent->_right == cur)
    44. parent->_bf++;
    45. else
    46. parent->_bf--;
    47. if (parent->_bf == 0)
    48. {
    49. //左右平衡了不会再影响祖先了
    50. break;
    51. }
    52. if (parent->_bf == 1 || parent->_bf == -1)
    53. {
    54. //当前节点所在子树变了,会影响父亲
    55. // 继续往上更新
    56. cur = parent;
    57. parent = parent->_parent;
    58. }
    59. else if (parent->_bf == 2 || parent->_bf == -2)
    60. {
    61. //parent所在子树已经不平衡,需要旋转处理一下
    62. if (parent->_bf == -2)
    63. {
    64. if (cur->_bf == -1)
    65. // 右单旋
    66. RotateR(parent);
    67. else // cur->_bf == 1
    68. RotateLR(parent);
    69. }
    70. else // parent->_bf == 2
    71. {
    72. if (cur->_bf == 1)
    73. // 左单旋
    74. RotateL(parent);
    75. else // cur->_bf == -1
    76. RotateRL(parent);
    77. }
    78. break;
    79. }
    80. else
    81. {
    82. // 插入节点之前,树已经不平衡了,或者bf出错。需要检查其他逻辑
    83. assert(false);
    84. }
    85. }
    86. return true;
    87. }

    插入整体逻辑:

    1. 如果还没有元素是一课空树,直接插入即可;如果有元素,按pair的first(key)和比较的节点比较结果为大说明为空的哪个位置在右边,和比较的节点比较的结果小说明为空的哪个位置在左边,如果相等说明已经有这个元素了,二叉搜索树不支持冗余返回一个pair类第一个成员为那个相同元素的map的迭代器和第二个成员为false的pair类迭代器;
    2. 不知道这个已经找到的位置在父节点的左边还是右边,需要判断一下,然后插入元素;
    3. 插入元素的后那么平衡因子将发生变化,为0说明这个父亲节点左右平衡不会影响其他节点,为1或者-1需要向上调整,为2或者-2说明已经不平衡需要旋转;
    • 节点右子树最长路径-左子树最长路径,右边插入节点就+,左边插入节点就-;

     3.1四种旋转(左单旋、右单旋、左右双旋、右左双旋)

    3.1.1左单旋

    1. 调用函数是传的参数是轴点
    2. 要保留轴点的父亲,以及调整三叉链
    3. 调整后原来的孩子和父亲(轴点)的平衡因子都置为0;
    1. void RotateR(Node* parent)
    2. {
    3. //轴点的左,孩子节点
    4. Node* subL = parent->_left;
    5. //孩子节点的右
    6. Node* subLR = subL->_right;
    7. //我的右当你(轴点)的左
    8. parent->_left = subLR;
    9. //调整三叉链
    10. if (subLR)
    11. subLR->_parent = parent;
    12. //你(轴点)做我的右
    13. subL->_right = parent;
    14. //调整三叉链
    15. Node* parentParent = parent->_parent;
    16. parent->_parent = subL;
    17. if (parent == _root)
    18. {
    19. _root = subL;
    20. _root->_parent = nullptr;
    21. }
    22. else
    23. {
    24. //轴点的父亲新的孩子节点
    25. if (parentParent->_left == parent)
    26. parentParent->_left = subL;
    27. else
    28. parentParent->_right = subL;
    29. subL->_parent = parentParent;
    30. }
    31. subL->_bf = parent->_bf = 0;
    32. }

    3.1.2右单旋

    1. 调用函数是传的参数是轴点
    2. 要保留轴点的父亲,以及调整三叉链
    3. 调整后原来的孩子和父亲(轴点)的平衡因子都置为0;
    1. void RotateL(Node* parent)
    2. {
    3. //轴点的右,孩子节点
    4. Node* subR = parent->_right;
    5. //孩子节点的左
    6. Node* subRL = subR->_left;
    7. //我的左当你(轴点)的右
    8. parent->_right = subRL;
    9. //调整三叉链
    10. if (subRL)
    11. {
    12. subRL->_parent = parent;
    13. }
    14. //你(轴点)做我的左
    15. subR->_left = parent;
    16. Node* parentparent = parent->_parent;
    17. parent->_parent = subR;
    18. if (parent == _root)
    19. {
    20. if (parentparent->_left == parent)
    21. parentparent->_left = subR;
    22. else
    23. parentparent->_right = subR;
    24. subR->_parent = parentparent;
    25. }
    26. else
    27. {
    28. subR->_parent = nullptr;
    29. _root = subR;
    30. }
    31. subR->_bf = parent->_bf = 0;
    32. }

     

     3.1.3左右双旋

    1. 调用左单旋是传的参数是轴点1,右单旋传的轴点2
    2. 平衡因子分3种情况,依靠3个被改变节点中最后一个来判断
    1. void RotateLR(Node* parent)
    2. {
    3. Node* subL = parent->_left;
    4. Node* subLR = subL->_right;
    5. int bf = subLR->_bf;
    6. RotateL(parent->_left);
    7. RotateR(parent);
    8. // ...平衡因子调节还需要具体分析
    9. if (bf == -1)
    10. {
    11. subL->_bf = 0;
    12. parent->_bf = 1;
    13. subLR->_bf = 0;
    14. }
    15. else if (bf == 1)
    16. {
    17. parent->_bf = 0;
    18. subL->_bf = -1;
    19. subLR->_bf = 0;
    20. }
    21. else if (bf == 0)
    22. {
    23. parent->_bf = 0;
    24. subL->_bf = 0;
    25. subLR->_bf = 0;
    26. }
    27. else
    28. {
    29. assert(false);
    30. }
    31. }

    依靠3个被改变节点中最后一个来判断

     

     3.1.4右左双旋 

    1. 调用右单旋是传的参数是轴点1,左单旋传的轴点2
    2. 平衡因子分3种情况,依靠3个被改变节点中最后一个来判断
    1. void RotateRL(Node* parent)
    2. {
    3. Node* subR = parent->_right;
    4. Node* subRL = subR->_left;
    5. int bf = subRL->_bf;
    6. RotateR(parent->_right);
    7. RotateL(parent);
    8. // 平衡因子更新
    9. if (bf == 1)
    10. {
    11. subR->_bf = 0;
    12. parent->_bf = -1;
    13. subRL->_bf = 0;
    14. }
    15. else if (bf == -1)
    16. {
    17. parent->_bf = 0;
    18. subR->_bf = 1;
    19. subRL->_bf = 0;
    20. }
    21. else if (bf == 0)
    22. {
    23. parent->_bf = 0;
    24. subR->_bf = 0;
    25. subRL->_bf = 0;
    26. }
    27. else
    28. {
    29. assert(false);
    30. }
    31. }

     总结

    1. 调用旋转的实参是轴点
    2. 左单旋:我的左当你的右,你(轴点)当我的左
    3. 右单旋:我的右当你的左,你(轴点)当我的右

  • 相关阅读:
    【HBuilder X】解决HBuilder X内置浏览器显示过大影响使用
    敏捷整洁之道
    Bootstrap中的utilities(工具类)
    计算机视觉 立体视觉极简一览
    基于交替迭代法的交直流混合系统潮流计算matlab程序iEEE9节点系统算例
    vuex实现简易购物车加购效果
    ElasticSearch(es)使用游标读取全部数据
    2022了你还不会『低代码』?数据科学也能玩转Low-Code啦! ⛵
    input标签,新增那些属性
    1.3常规信息系统集成技术
  • 原文地址:https://blog.csdn.net/m0_72964546/article/details/127634329