• AVL树的插入操作


    目录

     

    1. 什么是AVL树?

    1.1 AVL树结点的定义

     2. AVL树的插入

    1. 按照二叉搜索树的方式来插入

    2. 调节平衡因子

    3. AVL树的旋转

    3.1 新结点插入较高左子树的左边 - 右单旋

    3.2 新结点插入较高右子树的右边 - 左单旋

    3.3 新结点插入较高左子树的右边 -左右双旋

    3.4 新结点插入较高右子树的左边 -右左双旋

    总结


     

    1. 什么是AVL树?

    二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查
    找元素相当于在顺序表中搜索元素,效率低下。因此,两位俄罗斯的数学家G.M.Adelson-Velskii
    和E.M.Landis在1962年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。

    一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树:
    它的左右子树都是AVL树
    左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)

    97bfa87e8632481eadfb3920c4a7bd86.png

    1.1 AVL树结点的定义

    1. template<class K,class V>
    2. struct AVLTreeNode
    3. {
    4. pair _kv; //插入的数据
    5. AVLTreeNode* _left; //左孩子
    6. AVLTreeNode* _right;//右孩子
    7. AVLTreeNode* _parent;//双亲
    8. //右子树-左子树的高度差
    9. int _bf; //balance factor
    10. //构造函数
    11. AVLTreeNode(const pair& kv)
    12. :_kv(kv)
    13. ,_left(nullptr)
    14. ,_right(nullptr)
    15. ,_parent(nullptr)
    16. ,_bf(0)
    17. {}

     2. AVL树的插入

    AVL树的插入就是建立在二叉搜索树的基础上添加了平衡因子,因此AVL树也是二叉搜索树的一种,那么AVL树的插入分为两步:

    1. 按照二叉搜索树的方式来插入结点

    2. 调节结点的平衡因子

    1. 按照二叉搜索树的方式来插入

    1. //如果是首次插入
    2. if (!_root)
    3. {
    4. _root = new Node(kv);
    5. return true;
    6. }
    7. Node* cur = _root;
    8. Node* parent = nullptr;
    9. //这里和普通的二叉搜索树插入差不多
    10. while (cur)
    11. {
    12. if (cur->_kv.first < kv.first)
    13. {
    14. parent = cur;
    15. cur = cur->_right;
    16. }
    17. else if (cur->_kv.first > kv.first)
    18. {
    19. parent = cur;
    20. cur = cur->_left;
    21. }
    22. else
    23. {
    24. return false;
    25. }
    26. }
    27. cur = new Node(kv);
    28. if (kv.first < parent->_kv.first)
    29. {
    30. parent->_left = cur;
    31. }
    32. else
    33. {
    34. parent->_right = cur;
    35. }
    36. //只不过多了一个三叉链
    37. cur->_parent = parent;

    2. 调节平衡因子

    当新结点插入搜索树中可能回破坏AVL的平衡性,这时就需要更新平衡因子来判断是否破坏了平衡性。在插入新结点之前,父节点的平衡因子只能是0、1、-1这三种情况,而插入之后我们就需要先更新父节点的平衡因子:

    1. 如果插入的结点在父节点的左边 : 平衡因子 - 1

    2. 如果插入的结点在父节点的右边 : 平衡因子 + 1

    此时父节点的平衡因子可能有三种情况:0、正负1、正负2:

    1. 如果此时父节点的平衡因子的0,则说明插入结点后的高度没有发生改变,则不需要调整父节点以上的结点的平衡因子

    2. 覆盖此时父节点的平衡因子是正负1,则说明插入结点后高度发生了改变,需要调整父节点以上的结点的平衡因子

    3. 如果此时的父节点的平衡因子是正负2,则说明不满足AVL树的性质,需要对其进行旋转

    我们先写出更新平衡因子的代码:

    1. //更新平衡因子
    2. while (parent)
    3. {
    4. //先更新目前父节点的平衡因子
    5. if (cur == parent->_left)
    6. {
    7. parent->_bf--;
    8. }
    9. else
    10. {
    11. parent->_bf++;
    12. }
    13. //如果高度不变则退出
    14. if (parent->_bf == 0)
    15. {
    16. break;
    17. }
    18. //如果有一边变高了,则需要继续往上进行修改
    19. else if (parent->_bf == -1 || parent->_bf == 1)
    20. {
    21. cur = cur->_parent;
    22. parent = parent->_parent;
    23. }
    24. //如果超出了平衡因子,则需要进行旋转
    25. else if (parent ->_bf == 2 || parent-> _bf == -2)
    26. {
    27. //旋转操作....
    28. break;
    29. }
    30. //在插入之前,此树就已经发生了平衡因子失调的情况
    31. else
    32. {
    33. assert(false);
    34. }
    35. }

    那么接下来我们要考虑旋转的问题

    3. AVL树的旋转

    如果在一棵原本平衡的树中插入了一个新结点,可能会引起不平衡,此时必须调整树的结构,使其平衡化,而结点插入的位置不同,也导致了旋转分成了四类情况:

    3.1 新结点插入较高左子树的左边 - 右单旋

    9ccc7cc9a2f3479692e1f8cfa08907f2.png

     这里我们可以看见60这个结点已经不满足AVL树的性质了,那么我们要如何处理呢?

    首先60(parent)的位置就是我们的旋转点,60的左子树称为subL,subL的右子树称为subLR

    我们将parent的左子树指向subLR,subL的右子树指向parent:

    代码实现如下:

    1. //右右旋转
    2. //需要将左子树的右子树接在父节点的左子树上,左子树的右子树变成父节点
    3. void RotateR(Node* parent)
    4. {
    5. Node* subL = parent->_left;
    6. Node* subLR = subL->_right;
    7. //父节点接右子树的左子树
    8. parent->_left = subLR;
    9. if (subLR)
    10. {
    11. subLR->_parent = parent;
    12. }
    13. //左子树的右子树变成父节点
    14. Node* ppnode = parent->_parent;//父节点的父节点
    15. subL->_right = parent;
    16. parent->_parent = subL;
    17. //如果父节点是根节点
    18. if (parent == _root)
    19. {
    20. _root = subL;
    21. //根节点的父节点为空
    22. subL->_parent = nullptr;
    23. }
    24. //如果父节点不是根节点
    25. else
    26. {
    27. if (parent == ppnode->_left)
    28. {
    29. ppnode->_left = subL;
    30. }
    31. else
    32. {
    33. ppnode->_right = subL;
    34. }
    35. subL->_parent = ppnode;
    36. }
    37. //更新旋转后的平衡因子
    38. subL->_bf = 0;
    39. parent->_bf = 0;
    40. }

    3.2 新结点插入较高右子树的右边 - 左单旋

    b5aed1fea892488ea8ccc05ff0ddaf68.png

     这里我们可以看见30这个结点已经不满足AVL树的性质了,那么我们要如何处理呢?

    首先30(parent)的位置就是我们的旋转点,将30的右子树称为subR,subR的左子树称为subRL。

    我们将30的右子树指向subRL,将subR的左子树指向parent:

    228cc3fae8194433b5012981a13b312b.png

     代码实现如下:

    1. void RotateL(Node* parent)
    2. {
    3. Node* subR = parent->_right;
    4. Node* subRL = subR->_left;
    5. //父节点接右子树的左子树
    6. parent->_right = subRL;
    7. if (subRL)
    8. {
    9. subRL->_parent = parent;
    10. }
    11. //右子树的左子树变成父节点
    12. Node* ppnode = parent->_parent;//父节点的父节点
    13. subR->_left = parent;
    14. parent->_parent = subR;
    15. //如果父节点是根节点
    16. if (parent == _root)
    17. {
    18. _root = subR;
    19. //根节点的父节点为空
    20. subR->_parent = nullptr;
    21. }
    22. //如果父节点不是根节点
    23. else
    24. {
    25. if (parent == ppnode->_left)
    26. {
    27. ppnode->_left = subR;
    28. }
    29. else
    30. {
    31. ppnode->_right = subR;
    32. }
    33. subR->_parent = ppnode;
    34. }
    35. //更新旋转后的平衡因子
    36. subR->_bf = 0;
    37. parent->_bf = 0;
    38. }

    3.3 新结点插入较高左子树的右边 -左右双旋

     96673fcb17ea427aa56765f9b39fa0ed.png

    当我们的结点插入在b或者c位置或者当h=0时会发生平衡失调 

    实现步骤:

     先以30为旋转点进行左单旋,然后再以90为旋转点进行右单旋

    但是由于结点插入的位置有两个:一个是b一个是c,对于插入不同的位置,旋转后各个结点的平衡因子会发生变化

    首先我们来看插入在b的情况:

    31ae209d79404210866ffa30a06d0dea.png

    我们可以发现当插入在b位置时,旋转前60位置的平衡因子是-1,旋转后60(subLR)位置的平衡因子是-1时,30(subL)最后的平衡因子是0,90(parent)是1

    然后来看插入在c位置时的情况:

    6c980688ec0740aa8d51114f22be8b59.png

     我们可以发现当插入在c位置时,旋转前60位置的平衡因子是1,旋转后60(subLR)位置的平衡因子是0时,30(subL)最后的平衡因子-1,90(parent)是0

    最后来看当h为0的情况下:
    b4d1c14b3cd8450c9575dda1a4562a94.png

     我们可以发现当h==0时,旋转前60位置的平衡因子是0,旋转后60(subLR)位置的平衡因子是0时,30(subL)最后的平衡因子是0,90(parent)是0

    因此根据subLR的平衡因子的三种情况:1,-1,0,最后修改的平衡因子也不同

    代码实现如下:

    1. void RotateLR(Node* parent)
    2. {
    3. Node* subL = parent->_left;
    4. Node* subLR = subL->_right;
    5. int bf = subLR->_bf;
    6. RotateL(subL);
    7. RotateR(parent);
    8. if (bf == 0)
    9. {
    10. subL->_bf = 0;
    11. subLR->_bf = 0;
    12. parent->_bf = 0;
    13. }
    14. else if (bf == -1)
    15. {
    16. subL->_bf = 0;
    17. subLR->_bf = 0;
    18. parent->_bf = 1;
    19. }
    20. else if (bf == 1)
    21. {
    22. subL->_bf = -1;
    23. subLR->_bf = 0;
    24. parent->_bf = 0;
    25. }
    26. else
    27. {
    28. //在执行之前此树就有问题
    29. assert(false);
    30. }
    31. }

    3.4 新结点插入较高右子树的左边 -右左双旋

    53f541acd9d044429f2e576159f4c5ee.png

     当我们结点插入在b、c位置会发生失调

    实现步骤:

     先以90为旋转点进行右单旋,然后再以30为旋转点进行左单旋

    但是由于结点插入的位置有两个:一个是b一个是c,对于插入不同的位置,旋转后各个结点的平衡因子会发生变化

    首先我们来看插入在b的情况:

    8fa446cbfa8a4584801b70fc230e7df2.png

     我们可以发现当插入在b位置时,旋转前60位置的平衡因子是-1,旋转后60(subLR)位置的平衡因子是0时,30(subL)最后的平衡因子是0,90(parent)是1

    然后来看插入在c位置时的情况:5c6c616cb4764491bc15a459f639b9a1.png

     我们可以发现当插入在b位置时,旋转前60位置的平衡因子是-1,旋转后60(subLR)位置的平衡因子是0时,30(subL)最后的平衡因子是-1,90(parent)是0

    最后来看当h为0的情况下:

    1c2024bc29ac484c910677197fa73b6a.png

      我们可以发现当h==0时,旋转前60位置的平衡因子是0,旋转后60(subLR)位置的平衡因子是0时,30(subL)最后的平衡因子是0,90(parent)是0

    代码实现如下:

    1. void RotateRL(Node* parent)
    2. {
    3. Node* subR = parent->_right;
    4. Node* subRL = subR->_left;
    5. int bf = subRL->_bf;
    6. RotateR(subR);
    7. RotateL(parent);
    8. if (bf == 0)
    9. {
    10. subR->_bf = 0;
    11. subRL->_bf = 0;
    12. parent->_bf = 0;
    13. }
    14. else if (bf == -1)
    15. {
    16. subR->_bf = 1;
    17. subRL->_bf = 0;
    18. parent->_bf = 0;
    19. }
    20. else if (bf == 1)
    21. {
    22. subR->_bf = 0;
    23. subRL->_bf = 0;
    24. parent->_bf = -1;
    25. }
    26. else
    27. {
    28. //在执行之前此树就有问题
    29. assert(false);
    30. }
    31. }

    总结

    假如以parent为根的子树不平衡,即parent的平衡因子为2或者-2,分以下情况考虑
    1. parent的平衡因子为2,说明parent的右子树高,设parent的右子树的根为pSubR

    • 当pSubR的平衡因子为1时,执行左单旋
    • 当pSubR的平衡因子为-1时,执行右左双旋

    2. parent的平衡因子为-2,说明parent的左子树高,设parent的左子树的根为pSubL

    • 当pSubL的平衡因子为-1是,执行右单旋
    • 当pSubL的平衡因子为1时,执行左右双旋

    旋转完成后,原parent为根的子树个高度降低,已经平衡,不需要再向上更新。
    最后代码:

    1. bool insert(const pair& kv)
    2. {
    3. //1. 按照搜索树的规则插入
    4. //2. 看看是否违背了平衡因子
    5. if (!_root)
    6. {
    7. _root = new Node(kv);
    8. return true;
    9. }
    10. Node* cur = _root;
    11. Node* parent = nullptr;
    12. //这里和普通的二叉搜索树插入差不多
    13. while (cur)
    14. {
    15. if (cur->_kv.first < kv.first)
    16. {
    17. parent = cur;
    18. cur = cur->_right;
    19. }
    20. else if (cur->_kv.first > kv.first)
    21. {
    22. parent = cur;
    23. cur = cur->_left;
    24. }
    25. else
    26. {
    27. return false;
    28. }
    29. }
    30. cur = new Node(kv);
    31. if (kv.first < parent->_kv.first)
    32. {
    33. parent->_left = cur;
    34. }
    35. else
    36. {
    37. parent->_right = cur;
    38. }
    39. //只不过多了一个三叉链
    40. cur->_parent = parent;
    41. //更新平衡因子
    42. while (parent)
    43. {
    44. //先更新目前父节点的平衡因子
    45. if (cur == parent->_left)
    46. {
    47. parent->_bf--;
    48. }
    49. else
    50. {
    51. parent->_bf++;
    52. }
    53. //如果高度不变则退出
    54. if (parent->_bf == 0)
    55. {
    56. break;
    57. }
    58. //如果有一边变高了,则需要继续往上进行修改
    59. else if (parent->_bf == -1 || parent->_bf == 1)
    60. {
    61. cur = cur->_parent;
    62. parent = parent->_parent;
    63. }
    64. //如果超出了平衡因子,则需要进行旋转
    65. else if (parent ->_bf == 2 || parent-> _bf == -2)
    66. {
    67. if (parent->_bf == 2 && cur->_bf == 1)//左单旋
    68. {
    69. RotateL(parent);
    70. }
    71. if (parent->_bf == -2 && cur->_bf == -1)//右单旋
    72. {
    73. RotateR(parent);
    74. }
    75. if (parent->_bf == -2 && cur->_bf == 1)//左右双旋
    76. {
    77. RotateLR(parent);
    78. }
    79. if (parent->_bf == 2 && cur->_bf == -1)//右左双旋
    80. {
    81. RotateRL(parent);
    82. }
    83. break;
    84. }
    85. //在插入之前,此树就已经发生了平衡因子失调的情况
    86. else
    87. {
    88. assert(false);
    89. }
    90. }
    91. }

     

     

  • 相关阅读:
    CSS选择器分类( 通配符、标签选择器、id选择器、类选择器)
    【神印王座】陈樱儿假扮魔神皇,皓晨想杀人灭口,采儿施展禁制,月夜成功自保
    HTML期末学生大作业 基于HTML+CSS+JavaScript通用的后台管理系统ui框架模板
    【MySQL基础|第三篇】--- 详谈SQL中的DQL语句
    Spring Boot整合日志
    AXI协议详解(10)-非对齐传输
    【机器学习笔记】
    CyclicBarrier线程同步
    SpringBoot学习笔记(一)——初识别SpringBoot与框架整合
    uniapp 微信小程序分享功能 onShareAppMessage(options)
  • 原文地址:https://blog.csdn.net/Rinki123456/article/details/126214066