• 【C++】-- AVL树详解


    目录

    一、AVL树概念

    1.二叉搜索树的缺点 

    2.AVL树的概念 

    二、AVL树定义

    1.AVL树节点定义

     2.AVL树定义

    三、AVL树插入 

    1.插入节点

    2.控制平衡 

    (1)更新平衡因子

    (2)旋转

             ①右单旋

             ②左单旋

             ③左右双旋

             ④右左双旋

    四、AVL树查找

    五、AVL树高度 

     六、判断是否为AVL树

    七、AVL树遍历

    八、时间复杂度


    一、AVL树概念

    1.二叉搜索树的缺点 

     map/multimap/set/multiset的底层都按照二叉搜索树实现,但是在【C++】-- 搜索二叉树一文中已经了解到二叉搜索树的缺点在于,假如向树中插入的元素有序或者接近有序时,二叉搜索树就会退化成单支树,时间复杂度会退化成O(N),相当于在顺序表中搜索元素,效率低下。所以map/multimap/set/multiset的底层结构对二叉搜索树做了处理,采用平衡树来实现。

    2.AVL树的概念 

    如何避免二叉树搜索树会退化成单支树的缺点呢?

    向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。为什么高度差的绝对值不超过1而不是0呢?因为如果高度差的绝对值不超过0,那么二叉树就变成满二叉树了,因此绝对值不能超过1。这就引入了平衡二叉树的概念:

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

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

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

    二、AVL树定义

    由于要实现AVL树的增删改查,所以定义AVL树的节点,就需要定义parent,否则插入节点时,不知道要链接到树里面哪个节点下面。

    1.AVL树节点定义

    节点定义:

    1. #pragma once
    2. #include
    3. using namespace std;
    4. template<class K,class V>
    5. class AVLTreeNode
    6. {
    7. AVLTreeNode* _left;//左子树
    8. AVLTreeNode* _right;//右子树
    9. AVLTreeNode* _parent;//父亲
    10. int _bf;//平衡因子
    11. pair _kv;//节点
    12. AVLTreeNode(const pair&, kv)
    13. {
    14. :_left(nullptr)
    15. ,_right(nullptr)
    16. ,_parent(nullptr)
    17. ,_bf(0)
    18. , _kv(kv)
    19. }
    20. {}
    21. };

     2.AVL树定义

    1. template<class K,class V>
    2. struct AVLTree
    3. {
    4. typedef AVLTreeNode Node;
    5. public:
    6. //构造函数
    7. AVLTree()
    8. :_root(nullptr)
    9. {}
    10. void _Destroy(Node* root)
    11. {
    12. if (root == nullptr)
    13. {
    14. return;
    15. }
    16. _Destroy(root->_left);
    17. _Destroy(root->_right);
    18. delete root;
    19. }
    20. //重载operator[]
    21. V& operator[](const K& key)
    22. {
    23. pairbool> ret = Insert(make_pair(key, V()));
    24. return ret.first->_kv.second;
    25. }
    26. //析构函数
    27. ~AVLTree()
    28. {
    29. _Destroy(_root);
    30. _root = nullptr;
    31. }
    32. private:
    33. Node* _root;
    34. };

    三、AVL树插入 

    1.插入节点

    插入节点需要先判断树是否为空:

    (1)若为空,让该节点作为根节点

    (2)若不为空,分3种情况:

    ①key比当前节点小,向左走

    ②key比当前节点大,向右走

    ③相等,插入失败

    如果没找到节点,那么需要插入新节点

    1. bool Insert(const pair& kv)
    2. {
    3. //1.空树
    4. if (_root == nullptr)
    5. {
    6. _root = new Node(kv);
    7. return true;
    8. }
    9. //2.非空树
    10. Node* parent = _root, * cur = _root;
    11. while (cur)
    12. {
    13. if (cur->_kv.first > kv.first)//向左找
    14. {
    15. parent = cur;
    16. cur = cur->_left;
    17. }
    18. else if (cur->_kv.first < kv.first)//向右找
    19. {
    20. parent = cur;
    21. cur = cur->_right;
    22. }
    23. else//找到了
    24. {
    25. return false;
    26. }
    27. }
    28. //没找到,需要插入
    29. cur = new Node(kv);
    30. if (parent->_kv.first < cur->_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. return true;
    41. }

    2.控制平衡 

    (1)更新平衡因子

     一个节点的平衡因子是否需要更新,取决于它的左右子树的高度是否发生变化。插入一个节点,如果它的父亲的平衡因子需要更新,那么它所在这条路径的从父亲到根的所有节点的平衡因子都需要更新。因此

    ①如果新增节点是父亲的左子树,那么parent->_bf--

    ②如果新增节点是父亲的右子树,那么parent->_bf++

    更新后: 

            a.如果parent->_bf=0,则停止更新

            b.如果parent->_bf==1||-1,需要继续往上更新(说明以parent为根的子树高度变了,由0变成了1或-1,有可能导致parent的parent的平衡因子=2或=-2)

            c.如果parent->_bf=2||-2已经不平衡了,那么需要旋转处理

    1. //控制平衡
    2. //1.更新平衡因子
    3. while (cur != root)
    4. {
    5. if (parent->_left == cur)//新节点插入到parent左孩子,parent的左子树变高了,平衡因子-1
    6. {
    7. parent->_bf--;
    8. }
    9. else
    10. {
    11. parent->_bf++;
    12. }
    13. if (parent->_bf == 0)
    14. {
    15. //已经平衡,停止更新
    16. break;
    17. }
    18. else if(parent->_bf == 1 || parent->_bf == -1)
    19. {
    20. //说明以parent为根的子树高度变了,由0变成了1或-1,有可能影响parent的parent的平衡因子,需要继续往上更新
    21. cur = parent;
    22. parent = parent->_parent;
    23. }
    24. else if (parent->_bf == 2 || parent->_bf == -2)
    25. {
    26. //已经出现了不平衡,需要旋转处理
    27. if (parent->_bf == -2)
    28. {
    29. if (cur->_bf == -1)
    30. {
    31. //右单旋
    32. RotateR(parent);
    33. }
    34. }
    35. }
    36. else
    37. {
    38. //插入新节点之前,树已经不平衡了
    39. assert(false);
    40. }
    41. }

    (2)旋转

    旋转处理有4种:右单旋、左单旋、右左单旋、左右单旋

    ①右单旋

    将新节点插入到较高左子树的左侧,即左左-----右单旋

     插入新节点前,AVL树是平衡的,新节点插入到10的左子树,那么10的左子树增加了一层,导致以20为根的二叉树不平衡。为了让20平衡,只能让20的左子树的高度减小一层,并把10的右子树的高度增加一层。

    因此,要把10的左子树往上提,把20转下来,因为20比10大,只能把20放在10的右子树,10的右子树比10大,比20小,因此只能把10的右子树放在20的左子树。再更新节点平衡因子。

    抽象图:

     需要考虑的情况:

    (1)10的右孩子可能存在,也可能不存在

    (2)20可能是根节点,也可能是子树;如果是根节点,旋转后,要更新根节点。如果是子树,可能是左子树也可能是右子树,就把20原来的父亲的左或右指向10。

    1. void RotateR(Node* parent)
    2. {
    3. Node* subL = parent->_left;
    4. Node* subLR = nullptr;
    5. if (subL)
    6. {
    7. subLR = subL->_right;
    8. }
    9. //1.左子树的右子树变我的左子树
    10. parent->_left = subLR;
    11. if (subLR)
    12. {
    13. subLR->_parent = parent;
    14. }
    15. //左子树变父亲
    16. subL->_right = parent;
    17. Node* parentParent = parent->_parent;
    18. parent->_parent = subL;
    19. if (parent == _root)//parent是根
    20. {
    21. _root = subL;
    22. _root->_parent = nullptr;
    23. }
    24. else//parent不是根,是子树
    25. {
    26. if (parentParent->_left == parent)
    27. {
    28. //parent是自己父亲的左子树,将subL作为parent父亲的左孩子
    29. parentParent->_left = subL;
    30. }
    31. else
    32. {
    33. //parent是自己父亲的右子树,将subL作为parent父亲的右孩子
    34. parentParent->_right = subL;
    35. }
    36. //subL的父亲就是parent的父亲
    37. subL->_parent = parentParent;
    38. }
    39. //更新平衡因子
    40. subL->_bf = parent->_bf = 0;
    41. }

    具象图:

    h=0的情况:

    20变成10的左子树,10的左子树为空,不用考虑 

    h=1的情况:

    20变成10的右子树,10的右子树12变成20的左子树

     

    h=2的情况:

     20变成10的左子树,10的右子树12变成20的左子树​​​​​​​

    ②左单旋

     新节点插入到较高右子树的右侧,即右右-----左单旋

     插入新节点前,AVL树是平衡的,新节点插入到20的右子树,那么20的右子树增加了一层,导致以10为根的二叉树不平衡。为了让10平衡,只能让10的右子树的高度减小一层,并把20的左子树的高度增加一层。

    因此,要把20的右子树往上提,把10转下来,因为10比20小,只能把10放在20的左子树,20的左子树比20小,比10大,因此只能把20的左子树放在10的右子树。再更新节点平衡因子。

    抽象图:

    需要考虑的情况:

    (1)20的左孩子可能存在,也可能不存在

    (2)10可能是根节点,也可能是子树;如果是根节点,旋转后,要更新根节点。如果是子树,可能是左子树也可能是右子树,就把10原来的父亲的左或右指向20。

    1. void RotateL(Node* parent)
    2. {
    3. Node* subR = parent->_right;
    4. Node* subRL = nullptr;
    5. if (subR)
    6. {
    7. subRL = subR->_left;
    8. }
    9. //1.右子树的左子树变我的右子树
    10. parent->_right = subRL;
    11. if (subRL)
    12. {
    13. subRL->_parent = parent;
    14. }
    15. //2.右子树变父亲
    16. subR->_left = parent;
    17. Node* parentParent = parent->_parent;
    18. parent->_parent = subR;
    19. if (parent == _root)//parent是根
    20. {
    21. _root = parent;
    22. _root->_parent = nullptr;
    23. }
    24. else//parent不是根,是子树
    25. {
    26. if (parentParent->_left == parent)
    27. {
    28. //parent是自己父亲的左子树,将subR作为parent父亲的左孩子
    29. parentParent->_left = subR;
    30. }
    31. else
    32. {
    33. //parent是自己父亲的右子树,将subR作为parent父亲的右孩子
    34. parentParent->_right = subR;
    35. }
    36. //subR的父亲就是parent的父亲
    37. subR->_parent = parentParent;
    38. }
    39. //更新平衡因子
    40. subR->_bf = parent->_bf = 0;
    41. }

    具象图:

    h=0的情况:

    10变成20的左子树,20的左子树为空,不考虑

    h=1的情况:

    10变成20的左子树,20的左子树变成10的右子树

    h=2的情况: 

     10变成20的左子树,20的左子树变成10的右子树​​​​​​​

     ③左右双旋

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

    插入新节点前,AVL树是平衡的,新节点插入到20的左子树,那么20的左子树增加了一层,导致以30为根的二叉树不平衡。为了让30平衡,只能让30的左子树的高度减小一层。

    现在30左子树的高度是h+1,但是现在不能把10进行右单旋,因为要把10右单旋,那么20和30都必须处于10的右子树,这办不到。因此要分为两步:

    (1)先把10左单旋

    (2)再把20右单旋

    1. void RotateLR(Node* parent)
    2. {
    3. Node* subL = parent->_left;
    4. Node* subLR = subL->_right;
    5. int bf = 0;
    6. if (subLR)
    7. {
    8. bf = subLR->_bf;
    9. }
    10. RotateL(parent->_left);
    11. RotateR(parent);
    12. //平衡因子调节还需要分析
    13. if (bf == -1)
    14. {
    15. subL->_bf = 0;
    16. parent->_bf = 1;
    17. subLR->_bf = 0;
    18. }
    19. else if (bf == 1)
    20. {
    21. parent->_bf = 0;
    22. subL->_bf = -1;
    23. subLR->_bf = 0;
    24. }
    25. else if (bf == 0)
    26. {
    27. parent->_bf = 0;
    28. subL->_bf = 0;
    29. subLR->_bf = 0;
    30. }
    31. else
    32. {
    33. assert(false);
    34. }
    35. }

    ④右左双旋

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

    插入新节点前,AVL树是平衡的,新节点插入到30的右子树,那么30的右子树增加了一层,导致以10为根的二叉树不平衡。为了让10平衡,只能让10的右子树的高度减小一层。 

    现在10右子树的高度是h+1,但是现在不能把30进行左单旋,因为要把30左单旋,那么10和20都必须处于30的左子树,这办不到。因此要分为两步:

    (1)先把20右单旋

    (2)再把10左单旋

    1. void RotateRL(Node* parent)
    2. {
    3. Node* subR = parent->_right;
    4. Node* subRL = subR->_left;
    5. int bf = 0;
    6. if (subRL)
    7. {
    8. bf = subRL->_bf;
    9. }
    10. //先右旋再左旋
    11. RotateR(parent->_right);
    12. RotateL(parent);
    13. //平衡因子调节还需要具体分析
    14. if (bf == 1)
    15. {
    16. parent->_bf = -1;
    17. subR->_bf = 0;
    18. subRL->_bf = 0;
    19. }
    20. else if (bf == -1)
    21. {
    22. parent->_bf = 0;
    23. subR->_bf = 1;
    24. subRL->_bf = 0;
    25. }
    26. else if (bf == 0)
    27. {
    28. parent->_bf = 0;
    29. subR->_bf = 0;
    30. subRL->_bf = 0;
    31. }
    32. else
    33. {
    34. assert(false);
    35. }
    36. }

    四、AVL树查找

    查找比较简单:

    (1)如果key比当前节点大,那就继续向右查找;

    (2)如果key比当前节点小,那就继续向左查找;

    (3)如果key恰好等于当前节点,找到了

    1. Node* Find(const K& key)
    2. {
    3. Node* cur = _root;
    4. while (cur)
    5. {
    6. if (cur.first < key)//比当前节点大,向右查找
    7. {
    8. cur = cur->_right;
    9. }
    10. else if (cur.first > key)//比当前节点小,向左查找
    11. {
    12. cur = cur->_left;
    13. }
    14. else//等于当前节点,找到了
    15. {
    16. return cur;
    17. }
    18. }
    19. return nullptr;
    20. }

    五、AVL树高度 

    计算树高度肯定要递归计算:

    (1)计算左右子树的高度

    (2)谁的高度大,那AVL树的高度就为较高子树的高度+1

    1. //求树的高度
    2. int Height(Node* root)
    3. {
    4. if (root == nullptr)
    5. {
    6. return 0;
    7. }
    8. int leftHeight = Height(root->_left);
    9. int rightHeight = Height(root->_right);
    10. return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
    11. }

     六、判断是否为AVL树

    检查树是否是AVL树:

    (1)获取左右子树高度

    (2)根据左右子树高度计算平衡因子

    (3)当平衡因子<=2 || -2时就是平衡的

    1. bool _IsBanlance(Node* root)
    2. {
    3. if (root == nullptr)
    4. {
    5. return true;
    6. }
    7. int leftHeight = Height(root->_left);
    8. int rightHeight = Height(root->_right);
    9. //检查平衡因子是否正确
    10. if (rightHeight - leftHeight != root->_bf)
    11. {
    12. cout << "平衡因子异常:" << root->_kv.first << endl;
    13. return false;
    14. }
    15. return abs(rightHeight - leftHeight) < 2
    16. && _IsBanlance(root->_left)
    17. && _IsBanlance(root->_right);
    18. }
    19. //判断是否是AVL树
    20. bool IsAVLTree()
    21. {
    22. return _IsBanlance(_root);
    23. }

    七、AVL树遍历

    遍历也很简单:递归遍历左子树和右子树即可 

    1. void _InOrder(Node* root)
    2. {
    3. if (root == nullptr)
    4. {
    5. return;
    6. }
    7. _InOrder(root->_left);
    8. cout << root->_kv.first << ":" << root->_kv.second << endl;
    9. _InOrder(root->_right);
    10. }
    11. void InOrder()
    12. {
    13. _InOrder(_root);
    14. }

    八、时间复杂度

    AVL树的操作时,需要找到位置,因此时间复杂度为高度次,即O() 。

  • 相关阅读:
    Leetcode—2578.最小和分割【简单】
    数据结构记录(一)
    torch.utils.data
    mac 图文一步一步安装单节点etcd并配置可视化界面
    MyBioSource 多巴胺受体 D1 (DRD1),多克隆抗体方案
    应用在SMPS中的GaN/氮化镓
    大数据技术基础实验五:Zookeeper实验——部署ZooKeeper
    面试题:volatile能否保证线程安全
    学生HTML个人网页作业作品 HTML+CSS校园环保(大学生环保网页设计与实现)
    算法| ss 逻辑问题
  • 原文地址:https://blog.csdn.net/gx714433461/article/details/126581378