• AVL的代码剖析(c++)


    目录

    1.什么是AVL树

    2.平衡因子的引入

    3.旋转

    3.1左旋

    3.2右旋

    3.3左右双旋

    3.4右左双旋

    4.插入 

    5.AVL树的判平衡


    1.什么是AVL树

    AVL树是一棵二叉平衡搜索树。当向二叉搜索树中插入新的结点,如果能保证每个结点的左右子树高度之差的绝对值不超过1,那么此时就是二叉平衡搜索树。

    2.平衡因子的引入

    由于AVL是二叉平衡搜索树,所以AVL树的底层还是二叉搜索树,为了保证每个结点的左右子树高度之差的绝对值不超过1,我们在AVLNode中加入新的成员变量平衡因子——_bf

    1. template<class T, class V >
    2. struct AVLNode
    3. {
    4. pair _kv;
    5. AVLNode* left;
    6. AVLNode* right;
    7. AVLNode* parent;
    8. int _bf;
    9. AVLNode(const pair& kv)
    10. :_kv(kv)
    11. , right(nullptr)
    12. , left(nullptr)
    13. , parent(nullptr)
    14. , _bf(0)
    15. {}
    16. };

    pair _kv;  是库里的类模版,可以用来储存数据,这里不重要只需要了解pair的使用就行

    3.旋转

    为什么要旋转,因为每次插入数据的时候都会改变树某些节点的平衡因子,当树的两边的高度差超过1时,我们就需要通过旋转改变高度差,使得高度差<1。这里我们以右树的高度减去左树的高度为平衡因子的大小来衡量。旋转只有四种,左旋,右旋,左右旋,右左旋。首先我们来看左旋

    3.1左旋

    此时就是一种典型的左旋的情况。这里4节点的平衡因子是2,而5节点的平衡因子是1。此时4节点以及不平衡了。左旋,将5旋上去。我们就需要将平衡因子为2的节点传入函数中,我们称|_bf|>1的为parent节点,|_bf|<1的节点为cur节点。

    此时树就再一次平衡了用图来旋转是这样,那么我们看图写代码。我们可以知道,抽象的来说就是首先是将5的左边指向5的父亲4,而后将4的右边指向5的左边(这里5的左边是空,但有时5的左边是有节点的),将5的父亲指向4的父亲,4的父亲指向5.最后将5和4的平衡因子变成0就行了。

    具体代码如下

    1. void RotateL(Node* parent)//左单旋
    2. {
    3. Node* pcurR = parent->right;
    4. Node* curL = pcurR->left;
    5. Node* parent_parent = parent->parent;
    6. parent->parent = pcurR;
    7. pcurR->left = parent;
    8. parent->right = curL;
    9. pcurR->parent = parent_parent;
    10. if (curL)
    11. curL->parent = parent;
    12. if (parent_parent == nullptr)
    13. _root = pcurR;
    14. else
    15. {
    16. if (parent_parent->left == parent)
    17. parent_parent->left = pcurR;
    18. else
    19. parent_parent->right = pcurR;
    20. }
    21. parent->_bf = pcurR->_bf = 0;
    22. }

    3.2右旋

    右旋就是将左旋镜像一下。

    4的平衡因子变成-2,5的平衡因子变成-1。代码与左旋类似

    1. void RotateR(Node* parent)
    2. {
    3. Node* pcurR = parent->left;
    4. Node* curL = pcurR->right;
    5. Node* parent_parent = parent->parent;
    6. parent->parent = pcurR;
    7. pcurR->right = parent;
    8. parent->left = curL;
    9. pcurR->parent = parent_parent;
    10. if (curL)
    11. curL->parent = parent;
    12. if (parent_parent == nullptr)
    13. _root = pcurR;
    14. else
    15. {
    16. if (parent_parent->left == parent)
    17. parent_parent->left = pcurR;
    18. else
    19. parent_parent->right = pcurR;
    20. }
    21. parent->_bf = pcurR->_bf = 0;
    22. }

    3.3左右双旋

    有时候插入新的节点后单旋是不够的,此时就需要旋两次。具体看图

    无论怎么旋转都是通过单旋去实现平衡的。此时4的平衡因子是-2,3的平衡因子是-1。但是左右双旋不只是简单的直接复用左单旋和右单旋函数,上图情况没问题可以简单的复用,那下面这种情况就不能简单的复用了

    对比两图同样是左右双旋但是最后cur与parent的平衡因子却不一样,所以这里还需要讨论插入节点是左边还是右边,这里我们可以以curR为参考,插入左边时curR->_bf=-1。插入右边时curR->_bf=1。总的来看左右双旋只需要考虑3种情况。代码如下。

    1. void RotateLR(Node* parent)
    2. {
    3. Node* cur = parent->left;
    4. Node* curR = cur->right;
    5. int curR_bf = curR->_bf;
    6. RotateL(parent->left);
    7. RotateR(parent);
    8. if (curR_bf == 1)
    9. {
    10. cur->_bf = -1;
    11. parent->_bf = 0;
    12. curR->_bf = 0;
    13. }
    14. else if (curR_bf == -1)
    15. {
    16. cur->_bf = 0;
    17. parent->_bf = 1;
    18. curR->_bf = 0;
    19. }
    20. else if (curR_bf == 0)
    21. {
    22. cur->_bf = 0;
    23. parent->_bf = 0;
    24. curR->_bf = 0;
    25. }
    26. }

    3.4右左双旋

    同理右左双旋与左右双旋是镜像,代码逻辑还是一样,分3种情况,代码如下。

    1. void RotateRL(Node* parent)
    2. {
    3. Node* cur = parent->right;
    4. Node* curL = cur->left;
    5. int curL_bf = curL->_bf;
    6. RotateR(parent->right);
    7. RotateL(parent);
    8. if (curL_bf == 1)
    9. {
    10. cur->_bf = 0;
    11. parent->_bf = -1;
    12. curL->_bf = 0;
    13. }
    14. else if (curL_bf == -1)
    15. {
    16. cur->_bf = 1;
    17. parent->_bf = 0;
    18. curL->_bf = 0;
    19. }
    20. else if (curL_bf == 0)
    21. {
    22. cur->_bf = 0;
    23. parent->_bf = 0;
    24. curL->_bf = 0;
    25. }
    26. }

    4.插入 

    知道插入时调整平衡树的旋转逻辑后,插入就简单了,只需要根据parent和cur的平衡因子判断是哪一种情况就行。

    1. bool Insert(const pair& kv)
    2. {
    3. //插入肯定是从空开始插入的
    4. if (_root == nullptr)
    5. {
    6. _root = new Node(kv);
    7. return true;
    8. }
    9. //首先先找到要插入的位置
    10. Node* root = _root;
    11. Node* parent = nullptr;
    12. while (root)
    13. {
    14. if (root->_kv.first < kv.first)
    15. {
    16. parent = root;
    17. root = root->right;
    18. }
    19. else if (root->_kv.first > kv.first)
    20. {
    21. parent = root;
    22. root = root->left;
    23. }
    24. else
    25. {
    26. return false;
    27. }
    28. }
    29. Node* cur = new Node(kv);
    30. //右树-左树=平衡因子
    31. //先插入节点再去进行节点的更新
    32. if (kv.first > parent->_kv.first)//right
    33. {
    34. parent->right = cur;
    35. cur->parent = parent;
    36. }
    37. else//left
    38. {
    39. parent->left = cur;
    40. cur->parent = parent;
    41. }
    42. while (parent)
    43. {
    44. if (cur == parent->left)
    45. parent->_bf--;
    46. else
    47. parent->_bf++;
    48. if (parent->_bf==0)
    49. {
    50. break;
    51. }
    52. else if (parent->_bf == 1 || parent->_bf == -1)
    53. {
    54. cur = parent;
    55. parent = parent->parent;
    56. }
    57. else if (parent->_bf == 2 || parent->_bf == -2)
    58. {
    59. //开始旋转
    60. //旋转是有规律可言的,旋转总共只分为四种情况。
    61. if (parent->_bf == 2 && cur->_bf == 1)//左单旋
    62. {
    63. RotateL(parent);
    64. }
    65. else if (parent->_bf == -2 && cur->_bf == -1)//右单旋
    66. {
    67. RotateR(parent);
    68. }
    69. else if (parent->_bf == 2 && cur->_bf == -1)//先右单旋,再左单旋
    70. {
    71. RotateRL(parent);
    72. }
    73. else if (parent->_bf == -2 && cur->_bf == 1)//先左单旋,再右单旋
    74. {
    75. RotateLR(parent);
    76. }
    77. break;
    78. }
    79. else//判断树的逻辑是否正确,如果进入了这个else代表树的逻辑错了
    80. assert(false);
    81. }
    82. }

    5.AVL树的判平衡

    写完插入后我们需要判断是否AVL树平衡了。既然是二叉树,那么我们就可以用递归的方式去检查。使用分治的方式去写代码。

    1. bool _Is_BalanceTree(Node* root)
    2. {
    3. if (root == nullptr)
    4. return true;
    5. int left_hight = _Hight(root->left);
    6. int right_hight = _Hight(root->right);
    7. int hight = right_hight-left_hight;
    8. if (abs(hight) > 2 || hight != root->_bf)
    9. return false;
    10. return _Is_BalanceTree(root->left) && _Is_BalanceTree(root->right);
    11. }

    我们用分治的方式,考虑放回情况。当root为空时代表到某一条路径的尽头或者AVL树为空我们返回true,我们利用AVL树的定义去判断,我们要拿到root两边的子树高度差hight,进行判断hight的绝对值是否小于2,小于2返回true,大于2返回false,同时我们还需要看AVL树的平衡因子对了吗。然后我们再去递归判断左子树和右子树就行。这里我们需要再main函数中去调用bool _Is_BalanceTree()函数但是root是私有的不能再main函数中访问所以我们将_Is_BalanceTree函数作为子函数用一个bool Is_BalanceTree()函数去套用_Is_BalanceTree()函数,而且_Is_BalanceTree函数是我们不想被调用的我们就将其作为私有 这样就解决了。

    1. bool Is_BalanceTree()
    2. {
    3. return _Is_BalanceTree(_root);
    4. }

    最后我们还需要去写一个_hight函数去拿到树的高度,同意我们用递归的方式,用分治的思想去拿。

    1. int _Hight(Node* root)
    2. {
    3. if (root == nullptr)
    4. return 0;
    5. int left_hight = _Hight(root->left);
    6. int right_hight = _Hight(root->right);
    7. return (left_hight > right_hight ? left_hight +1: right_hight + 1);
    8. }

    最后return (left_hight > right_hight ? left_hight +1: right_hight + 1);的原因是树的高度是指最长的那一条路径,所以我们需要拿到左右中大的那一个,最后还要+1为什么呢,因为拿到的只是子树的高度还要算上自己的高度,自己作为树的“根”肯定是占一格高度的。

    至此AVL树的实现就结束了,希望能帮助到你,有错误的或者疑问的可以私信我。

  • 相关阅读:
    四位十进制频率计VHDL,DE1开发板验证,仿真和源码
    [计算机通信网络]关于ip地址、MAC地址、端口地址的抽象理解与实例
    win32-注册表-32位-64位-读写值-Qt-C++
    【Linux系统KVM虚拟机实战】LVM逻辑卷之磁盘扩容
    php实战案例记录(14)$_FILES函数的用法
    函数式编程:Rust中的闭包与迭代器
    哪款蓝牙运动耳机性价比最高、性价比最高的蓝牙运动耳机
    【面试补漏】vue.$nextTick的原理
    逻辑扇区和物理扇区
    【经典算法学习-排序篇】顺序查找
  • 原文地址:https://blog.csdn.net/yanlou233/article/details/141099500