• C++---AVL树


    目录

    1 AVL 树

    1.1 AVL树的概念

    1.2 AVL树节点的定义

    1.3 AVL树的插入

    旋转总体分为四种情况:

    1. 新节点插入较高右子树的右侧---右右:左单旋​

     2. 新节点插入较高左子树的左侧---左左:右单旋​

    3. 新节点插入较高左子树的右侧---左右:先左单旋再右单旋​

    4. 新节点插入较高右子树的左侧---右左:先右单旋再左单旋

    1 AVL

    1.1 AVL树的概念

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

            ~它的左右子树都是AVL树
            ~左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1),(平衡因子非必需,但是加入平衡因子更容易实现)

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

    1.2 AVL树节点的定义

    AVL树节点的定义:

    1. template<class K,class V>
    2. struct AVLTreeNode//定义成三叉链有助于后期旋转
    3. {
    4. AVLTreeNode(const pair& kv)
    5. :_left(nullptr)
    6. ,_right(nullptr)
    7. ,_parent(nullptr)
    8. ,_kv(kv)
    9. ,_bf(0)
    10. {}
    11. AVLTreeNode* _left;//指向左子树
    12. AVLTreeNode* _right;//指向右子树
    13. AVLTreeNode* _parent;//指向父节点
    14. pair _kv;//节点值
    15. int _bf;//balance factor平衡因子
    16. };

    1.3 AVL树的插入

            AVL树就是在二叉搜索树的基础上引入了平衡因子,因此AVL树也可以看成是二叉搜索树。那么AVL树的插入过程可以分为两步:
            1. 按照二叉搜索树的方式插入新节点
            2. 调整节点的平衡因子

    首先,按照二叉搜索树方式插入新节点:

    1. template<class K, class V>
    2. struct AVLTree
    3. {
    4. typedef AVLTreeNode Node;
    5. public:
    6. bool Insert(const pair& kv)
    7. {
    8. //先按搜索二叉树插入
    9. if (_root == nullptr)
    10. {
    11. _root = new Node(kv);
    12. return true;
    13. }
    14. Node* parent = nullptr;
    15. Node* cur = _root;
    16. //找插入位置
    17. while (cur)
    18. {
    19. if (cur->_kv.first < kv.first)
    20. {
    21. parent = cur;
    22. cur = cur->_right;
    23. }
    24. else if (cur->_kv.first > kv.first)
    25. {
    26. parent = cur;
    27. cur = cur->_left;
    28. }
    29. else
    30. {
    31. return false;
    32. }
    33. }
    34. //插入
    35. cur = new Node(kv);
    36. if (parent->_kv.first < kv.first)
    37. {
    38. parent->_right = cur;
    39. }
    40. else
    41. {
    42. parent->_left = cur;
    43. }
    44. cur->_parent = parent;
    45. //接下来控制平衡
    46. return true;
    47. }
    48. private:
    49. Node* _root = nullptr;
    50. };

    那么,如何控制平衡呢?

    首先,插入以后,我们要更新平衡因子:

     

    1. //1.更新平衡因子
    2. while (parent)
    3. {
    4. if (cur == parent->_right)
    5. {
    6. parent->_bf++;
    7. }
    8. else
    9. {
    10. parent->_bf--;
    11. }
    12. if (parent->_bf == 0)
    13. {
    14. break;
    15. }
    16. else if (abs(parent->_bf) == 1)
    17. {
    18. parent = parent->_parent;
    19. cur = cur->_parent;
    20. }
    21. else if (abs(parent->_bf) == 2)
    22. {
    23. //说明parent所在子树不平衡,需要旋转
    24. }
    25. else
    26. {
    27. //理论不会走到这,除非插入前就不平衡
    28. assert(false);
    29. }
    30. }

    更新完平衡因子,我们需要在parent->_bf == 2 的地方旋转树,使之平衡。

    旋转总体分为四种情况

    1. 新节点插入较高右子树的右侧---右右:左单旋

    上图中插入前AVL树是平衡的,a,b,c表示高度为 h 的子树(并不是节点),h 为 0 则表示直接在节点为30的左子树直接插入。

    1. void RotateL(Node* parent)
    2. {
    3. Node* subR = parent->_right;
    4. Node* subRL = subR->_left;
    5. parent->_right = subRL;
    6. if (subRL)//subRL可以为空,所以加条件
    7. {
    8. subRL->_parent = parent;
    9. }
    10. //parnet 可能为根也可以不是,记录一下
    11. Node* ppNode = parent->_parent;
    12. subR->_left = parent;
    13. parent->_parent = subR;
    14. if (_root == parent)
    15. {
    16. _root = subR;
    17. subR->_parent = nullptr;
    18. }
    19. else
    20. {
    21. if (ppNode->_left == parent)
    22. {
    23. ppNode->_left = subR;
    24. }
    25. else
    26. {
    27. ppNode->_right = subR;
    28. }
    29. subR->_parent = ppNode;
    30. }
    31. //更新平衡因子
    32. subR->_bf = parent->_bf = 0;
    33. }

     2. 新节点插入较高左子树的左侧---左左:右单旋

      右单旋与左单旋刚好对称,方法类似。         

    1. void RotateR(Node* parent)
    2. {
    3. Node* subL = parent->_left;
    4. Node* subLR = subL->_right;
    5. parent->_left = subLR;
    6. if (subLR)//subLR可以为空,所以加条件
    7. {
    8. subLR->_parent = parent;
    9. }
    10. Node* ppNode = parent->_parent;
    11. subL->_right = parent;
    12. parent->_parent = subL;
    13. if (_root == parent)
    14. {
    15. _root = subL;
    16. subL->_parent = nullptr;
    17. }
    18. else
    19. {
    20. if (ppNode->_left == parent)
    21. {
    22. ppNode->_left = subL;
    23. }
    24. else
    25. {
    26. ppNode->_right = subL;
    27. }
    28. subL->_parent = ppNode;
    29. }
    30. //更新平衡因子
    31. subL->_bf = parent->_bf = 0;
    32. }

    3. 新节点插入较高左子树的右侧---左右:先左单旋再右单旋

            将双旋变成单旋后再旋转,即:先对30进行左单旋,然后再对90进行右单旋,旋转完成后再
    考虑平衡因子的更新。
            双旋的平衡因子要分 在b子树插入在c子树插入以及a,b,c,d为空数 三种情况。

    1. void RotateLR(Node* parent)
    2. {
    3. //记录节点,用于后面改平衡因子
    4. Node* subL = parent->_left;
    5. Node* subLR = subL->_right;
    6. int bf = subLR->_bf;
    7. RotateL(parent->_left);
    8. RotateR(parent);
    9. //更新平衡因子
    10. subLR->_bf = 0;
    11. if (bf == 1)
    12. {
    13. parent->_bf = 0;
    14. subL->_bf = -1;
    15. }
    16. else if (bf == -1)
    17. {
    18. parent->_bf = 0;
    19. subL->_bf = 1;
    20. }
    21. else if (bf == 0)
    22. {
    23. parent->_bf = 0;
    24. subL->_bf = 0;
    25. }
    26. else
    27. {
    28. //理论不会出现
    29. assert(false);
    30. }
    31. }

    4. 新节点插入较高右子树的左侧---右左:先右单旋再左单旋

     方法与 先左单旋再右单旋 相反。

    1. void RotateRL(Node* parent)
    2. {
    3. //记录节点,用于后面改平衡因子
    4. Node* subR = parent->_right;
    5. Node* subRL = subR->_left;
    6. int bf = subRL->_bf;
    7. RotateR(parent->_right);
    8. RotateL(parent);
    9. //更新平衡因子
    10. subRL->_bf = 0;
    11. if (bf == 1)
    12. {
    13. subR->_bf = 0;
    14. parent->_bf = -1;
    15. }
    16. else if (bf == -1)
    17. {
    18. subR->_bf = 1;
    19. parent->_bf = 0;
    20. }
    21. else if (bf == 0)
    22. {
    23. parent->_bf = 0;
    24. subR->_bf = 0;
    25. }
    26. else
    27. {
    28. //理论不会出现
    29. assert(false);
    30. }
    31. }

  • 相关阅读:
    5.32 综合案例2.0 - TTS语音云播报(支持M320开发板)
    Day2 数据分析 Excel-基础函数【零基础】
    第十二届蓝桥杯《杨辉三角》-二分法
    web端动效 lottie-web 使用
    面试:linux相关
    【Python】第八课 异常处理
    Vue把常用 【组件提取】 出来后使用【插槽把父元素】传入【组件中】
    HTML期末作业课程设计期末大作业--小米网站开发者平台首页 1页
    2731. 移动机器人
    无声的世界,精神科用药并结合临床的一些分析及笔记(五)
  • 原文地址:https://blog.csdn.net/blue_jun_/article/details/127600910