当前位置:   article > 正文

详解AVL树(平衡二叉树)

avl树

目录

一.AVL树的概念与性质

1.1概念

1.2性质

二.AVL树操作

2.1AVL树节点定义

2.2AVL树插入

1.插入节点

2.保持树的平衡

3.更新parent平衡因子

三.AVL树的性能


我们在使用map/multimap/set/multiset这些容器时,有个共同点是:其底层都是按照二叉搜索树来实现的,但是二叉搜索树有其自身的缺陷,假如往树中插入的元素有序或者接近有序,二叉搜索树就会退化成单支树,时间复杂度会退化成O(N),因此map、set等关联式容器的底层结构是对二叉树进行了平衡处理,即采用平衡树来实现。

一.AVL树的概念与性质

1.1概念

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

1.2性质

它具有以下性质:

  • 他的左右子树都是AVL树
  • 左右子树高度差(又称平衡因子)的绝对值不超过1
  • 如果一颗二叉搜索树是高度平衡的,他就是AVL树,如果他有n个节点,其高度可保持在O(log2n),搜索时间复杂度O(log2n)。

二.AVL树操作

AVL树的创建,进行插入与删除节点,同时进行平衡判断。(我在分析时,会将代码拆开来,而且只会写核心的部分,最终代码我会附上链接(C++实现链接),需要的直接去拿。)

2.1AVL树节点定义

  1. struct AVLTreeNode{
  2. AVLTreeNode* left;
  3. AVLTreeNode* right;
  4. AVLTreeNode* parent;
  5. T data;
  6. int bf;//VAL树中的平衡因子
  7. AVLTreeNode(const T& x)
  8. :left(nullptr)
  9. , right(nullptr)
  10. , parent(nullptr)
  11. , data(x)
  12. , bf(0){}
  13. };

2.2AVL树插入

1.插入节点

我们在对AVL树进行插入的时候,其实就是二叉搜索时的插入。

  1. bool insert(const T& data){
  2. //先判断是否为空树
  3. if(nullptr == root){
  4. root = new Node(data);
  5. return true;
  6. }
  7. Node* cur = root;
  8. Node* parent = nullptr;
  9. while(cur){
  10. parent = cur;
  11. if(data < cur->data){
  12. cur =cur->left;
  13. }else if (data > cur->data){
  14. cur = cur ->right;
  15. }else{
  16. return false;
  17. }
  18. }
  19. //当找到要插入的位置时,进行插入
  20. cur = new Node(data);
  21. //如果此时插入的节点是大于父节点的,将其插入父节点的左边
  22. if(parent->data > data) parent->left = cur;
  23. else parent->right = cur;
  24. return true;
  25. }

2.保持树的平衡

插入节点,如果此时的树满足平衡,那么就不用进行调节,但是如果此时树不再保持平衡,就要对树进行旋转调整,分为四种情况。(我在分析时,会将代码拆开来,而且只会写核心的部分,最终代码我会附上链接(C++实现),需要的直接去拿。)

(1)右单旋

发生的情况:当一个平衡二叉树满足具有较高左子树时(如下图A),且要插入的节点插入到了较高左子树的左侧(此时注意是左子树,并非左孩子,因此当插入节点20为30的有孩子,其操作是相同的),此时进行右单旋。

我们根据上图进行分析,在进行旋转时,第一步先将parent->left指向curR节点,此时并要对curR的parent进行更新,但是必须注意的是此时curR是否为空,如果此时curR为空再对他进行操作可能代码会崩溃,如下代码是对curR进行更新。

  1. parent->left = curR;
  2. //因为我们使用的是孩子双亲表示法,因此更新curR节点的parent
  3. //此时curR不为空时,才能对其进行更新
  4. if(curR) curR->parent = parent;

 第一步操作结束,下来让cur->right指向parent节点,此时我们并不知道parent节点是否为之前树的根节点,因此要先将其父节点进行保存,后面要进行判断,再对插入的节点旋转结束后,对其旋转因子再进行更新。下面是第二步代码:

  1. cur->right = parent;
  2. Node* pparent = parent->parent;
  3. //现在我们先更新parent节点的父节点
  4. parent->parent = cur;
  5. //再更新cur的父节点
  6. cur->parent = pparent;
  7. //此时我们要注意,判断pparent是否为空,如果为空,那么root直接就是cur,如果不为空在进行判断他是父节点的左子树还是右子树
  8. if(pparent == nullptr)//说明此时更新节点后,cur应该为根节点
  9. root = cur;
  10. else if(pparent->left == parent)//说明之前是根节点的左子树
  11. pparent->left = cur;
  12. else//说明之前是右子树
  13. pparent->right = cur;
  14. parent->bf = 0;
  15. cur->bf = 0;

(2)左单旋

发生的情况:当一个平衡二叉树满足具有较高右子树时(如下图A),且要插入的节点插入到了较高右子树的右侧(此时注意是右子树,并非有孩子,因此,其操作是相同的),此时进行左单旋。

 我们根据上图进行分析,当开始进行左单旋时,我们先将parent节点进行处理,如下代码:

  1. parent->right = curL;
  2. //因为我们使用的是孩子双亲表示法,因此更新curL节点的parent
  3. //此时curL不为空时,才能对其进行更新
  4. if(curL) curL->parent = parent;

当处理完第一步之后,开始对cur节点进行处理,此时我们依旧要注意parent节点在左单旋之前是否是由父节点的,如下代码:

  1. //在这里先创建parent的父节点,待会进行判断
  2. Node* pparent = parent->parent;
  3. cur->left = parent;
  4. //更改parent的父节点
  5. parent->parent = cur;
  6. cur->parent = pparent;
  7. //此时上面的代码将parent与curL两个节点的父节点都处理完毕,
  8. //此时我们就要考虑cur此时是否有父节点,且是父节点的那个子树
  9. if(pparent == nullptr)//此时parent的之前的父节点为空,那么此时cur直接就是根节点
  10. root = cur;
  11. else if(pparent->left == parent)
  12. pparent->left = cur;
  13. else
  14. ppanret->right = cur;
  15. cur->bf = 0;
  16. parent->bf = 0;

(3)左右双旋与右左双旋

左右双旋发生的情况:当原本的平衡二叉树是满足具有较高的左子树时,此时带插入的节点插入到了较高左子树的右侧(内侧),如下图,此时就要使用左右双旋。

我们在这里发现,他与要进行右单旋的情况有些像,但是在向AVL树中进行插入时,左子树的内侧,此时我们先试一试右单旋,观察他是否可以解决平衡问题,如下图。

 我们观察到上面的图,发现虽然这种情况下与右单旋的情况有些相似,但是不能够达到平衡的情况,因此我们就要重新考虑如何旋转使之平衡。如下图所示:

我们观察上图,可以先利用左单旋将parent的左子树进行调整,将其转化成结构B,此时的二叉树的结构符合右单旋的情况,因此再进行右单旋,最终满足平衡的情况,因此此时需要先进行左单旋,再进行右单旋。我们再看图D到图E的平衡因子的变化。因为,我们在前两次的左单旋与右单旋之后都对他当前的parent与cur的平衡因子进行的更改,在最后在curR上插入新节点的位置会引起不同的平衡因子的更新。此时的代码就是复用上面的代码。因此在这里就不再重复。

右左双旋发生的情况:当原本的平衡二叉树是满足具有较高的右子树时,此时带插入的节点插入到了较右子树的左侧(外侧),此时就要使用右左双旋。他的思路与左右双旋的思路相同,但是右左双旋实现将parent节点的右子树通过左单旋进行调整,使其满足左单旋的情况,再进行左单旋。此时二叉树就会满足平衡。

3.更新parent平衡因子

在插入之前,平衡因子是满足情况的,当插入了cur节点之后,我们要从cur插入的位置向上更新parent的平衡因子,在更新时,我们只需要判断cur被插入到了parent的那棵子树,插入到左子树时,对parent->buf--;插入到右子树时,对parent->buf++。代码如下:

  1. while(parent){//当parent不为空,那就相当于要遍历到二叉树的根节点即可
  2. if(parent->right == cur)//说明此时插入到了右子树的位置
  3. parent->bf++;
  4. else//说明此时插入到了左子树的位置
  5. parent->bf--;
  6. if( parent->bf == 0)//这里就是以parent为根的二叉树高度没有改变,对上层不会用影响
  7. break;
  8. else if(1 == parent->bf || -1 == parent->bf){
  9. //以parent为根的二叉树的高度增建了,需要继续往上层更新
  10. cur = parent;
  11. parent = parent->parent;
  12. }else{//此时已经违反了AVL树的特性
  13. if(parent->bf == -2){
  14. if(cur->bf == -1){
  15. //此时进行右单旋
  16. }else{
  17. //此时进行左右双旋
  18. }
  19. else{
  20. if(cur->bf == -1){
  21. //此时进行右左双旋
  22. }else{
  23. //此时进行左单旋
  24. }
  25. break;
  26. }
  27. }
  28. }

4.AVL树验证

 AVL树是在二叉搜索树的基础上加入了平衡性的限制,因此要验证AVL树,可以分两步:

  •  验证其为二叉搜索树 如果中序遍历可得到一个有序的序列,就说明为二叉搜索树
  •  验证其为平衡树 每个节点子树高度差的绝对值不超过1(注意节点中如果没有平衡因子) 节点的平衡因子是否计算正确
  1. //求AVL树的深度(高度)
  2. int get_hight(node* root){
  3. if (root == nullptr) return 0;
  4. int hightleft = get_hight(root->left);
  5. int hightright = get_hight(root->right);
  6. int hight = hightleft > hightright ? hightleft + 1 : hightright + 1;
  7. return hight;
  8. }
  9. //判断是否是AVLTree
  10. bool isAVLTree(node* root){
  11. if (nullptr == root) return true;
  12. int hightleft = get_hight(root->left);
  13. int hightright = get_hight(root->right);
  14. int bf = hightright - hightleft;
  15. if (abs(root->bf) > 1 || bf != root->bf){//当平衡因子的绝对值大于等于2,或者在更新平衡因子时出错,都返回false
  16. cout << root->data << ":" << bf << "--" << root->bf << endl;
  17. return false;
  18. }
  19. return isAVLTree(root->left) && isAVLTree(root->right);//在进行检测他的左右子树
  20. }

三.AVL树的性能

AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这样可以保证查询时高效的时间复杂度,即log2N 。但是如果要对AVL树做一些结构修改的操作,性能非常低下,比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。 因此:如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树, 但一个结构经常修改,就不太适合。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/492091
推荐阅读
相关标签
  

闽ICP备14008679号