详解-04树5 Root of AVL Tree

题目来源:
中国大学MOOC-陈越、何钦铭-数据结构

题目详情:

译文:
AVL树是一种自平衡二叉搜索树。 在AVL树中,任何节点的两个子树的高度最多相差1。 如果它们之间的高度差超过1,则将进行重新平衡以恢复此属性。 图1-4为旋转规则。
现在给出了一系列插入,返回得到的AVL树的根结点。

输入格式:
每个输入文件包含一个测试用例。 对于每种情况,第一行都包含一个正整数N(≤20),它是要插入的键的总数。 然后在下一行中给出N个不同的整数键。 一行中的所有数字都用空格分隔。

输出格式:
对于每个测试用例,将AVL树的根打印在一行中。

输入样例1:

5
88 70 61 96 120

输出样例1:

70

输入样例2:

7
88 70 61 96 120 90 65

输出样例2:

88

解题思路:
AVL树是一棵动态的树,即在新的结点插入的过程中,它会自动平衡使自己满足任意结点的左右子树高度差不大于1,因此题目输入样例给出AVL树的每个结点值我们可以“在线”插入,然后根据插入数据后树的状态进行调整。具体怎么调整就要引入2个概念,即破坏平衡的发现者破坏者,我们调整AVL树就是针对这两个结点进行调整。

破坏者:顾名思义,即为新插入了一个结点,该结点使得树变得不满足任意结点的左右子树的高度差不大于1这条性质,那么这个结点成为“破坏者”。

发现者:即离“破坏者”最近的,左右子树高度差大于1这条性质被破坏的结点。

直观地说明的话,参考题目中的Fig1,因为最后插入了结点61,导致结点88的左子树高度为2,右子树的高度为0,因此破坏了AVL树的性质,所以结点61就是“破坏者”,结点“88”就是发现者。

知道了这两个概念后,再看如何将平衡被破坏的AVL调平衡,再看Fig的调整方式,它是将结点70提上来作为根节点,把结点88放下去左右结点70的右子树,相当于将该树按着顺时针的方向旋转了一下,这种方法成为左单旋(LL旋转),为什么叫左单旋呢,看LL旋转更好理解,因为破坏者(即结点61)是位于发现者(即结点88)的左子树的左边,因此为LL旋转。具体的做法就是将破坏者的父结点移到发现者的位置上,将发现者移到破坏者父结点的右儿子的位置上,因为破坏者父结点的右儿子一定是空,并且需要右儿子的值大于父结点,这么做刚好满足AVL树的性质,并且还是一棵搜索树。

同理,看Fig2,因为结点120的插入,导致平衡被破坏,破坏者为结点120,发现者为结点88,因此我们要调平衡的话就要使用右单旋(RR旋转),因为破坏者位于发现者的右子树的右边。做法参考LL旋转,即将破坏者的父结点移到发现者的位置上,将发现者移到破坏者父结点左儿子的位置上。

还有剩下的两种情况,即破坏者位于发现者的左子树的右边和破坏者位于发现者的右子树的左边,我们分别可以使用LR旋转(先做一次RR旋转再做一次LL旋转)和RL旋转(先做一次LL旋转,再做一次RR旋转)。图Fig4即是采用LR旋转恢复平衡,因为结点65的插入导致树的平衡被破坏,因此结点65是破坏者,而离结点65最近的被破坏平衡的结点是结点70,因此结点70为发现者,而结点65位于结点70的左子树的右边,因此做LR旋转。

以上就是如何将被破坏平衡的AVL树恢复平衡,4个旋转的方式
具体的旋转方法可以参考程序。

完整的程序如下:

#include <stdio.h>
#include <stdlib.h>

#define max(x, y) (x >= y ? x : y) //定义的宏,用来取最大值

typedef struct AVLNode *Tree;
typedef struct AVLNode{
    int val; //结点的值
    Tree left; //左子树
    Tree right; //右子树
    int height; //该结点的高度
}Node;

Tree insert(Tree AVL, int val);
Tree leftRotation(Tree T1);
Tree leftRightRotation(Tree T1);
Tree rightRotation(Tree T1);
Tree rightLeftRotation(Tree T1);
int getHeight(Tree T1);

int main(){
    int i, N;
    scanf("%d", &N); //输入的N
    if(N){
        Tree AVL = NULL;
        int val;
        for(i=0; i<N; i++){
            scanf("%d", &val);
            AVL = insert(AVL, val); //在线插入结点
        }
        printf("%d\n", AVL->val); //输出根节点的值
    }
    return 0;
}

Tree insert(Tree AVL, int val){
    if(!AVL){ //若树空,则创建一个空树
        AVL = (Tree)malloc(sizeof(Node));
        AVL->val = val;
        AVL->left = AVL->right = NULL;
        AVL->height = 0;
    }
    else if(AVL->val > val){ //树不空且新插入的结点值小,应该往左子树插
        AVL->left = insert(AVL->left, val); //递归直到最底层插入新结点
        if(getHeight(AVL->left) - getHeight(AVL->right) == 2){ //平衡被破坏
            if(AVL->left->val > val) //插入的值小于左子树,即插在左子树的左边
                AVL = leftRotation(AVL); //执行LL旋转
            else //插在了左子树的右边
                AVL = leftRightRotation(AVL); //执行LR旋转
        }
    }
    else if(AVL->val < val){ //树不空且新插入的结点值大,应该往右子树插
        AVL->right = insert(AVL->right, val); //递归直到最底层插入新结点
        if(getHeight(AVL->left) - getHeight(AVL->right) == -2){ //平衡被破坏
            if(AVL->right->val < val) //插入的值大于右子树,即插在右子树的右边
                AVL = rightRotation(AVL); //执行RR旋转
            else //插在了右子树的左边
                AVL = rightLeftRotation(AVL); //执行RL旋转
        }
    }
    //更新树高
    AVL->height = max(getHeight(AVL->left), getHeight(AVL->right)) + 1;
    
    return AVL;
}

Tree leftRotation(Tree T1){
    Tree T2 = T1->left; //记录破坏者的父结点
    T1->left = T2->right; //将破坏者父结点的右子树接到发现者的左子树上
    T2->right = T1; //将破坏者的父结点移动到发现者的位置上
    //更新树高
    T1->height = max(getHeight(T1->left), getHeight(T1->right)) + 1;
    T2->height = max(getHeight(T2->left), T1->height) + 1;
    
    return T2;
}

Tree leftRightRotation(Tree T1){
    T1->left = rightRotation(T1->left);
    
    return leftRotation(T1);
}

Tree rightRotation(Tree T1){
    Tree T2 = T1->right;
    T1->right = T2->left;
    T2->left = T1;
    T1->height = max(getHeight(T1->left), getHeight(T1->right)) + 1;
    T2->height = max(getHeight(T2->right), T1->height) + 1;
    
    return T2;
}

Tree rightLeftRotation(Tree T1){
    T1->right = leftRotation(T1->right);
    
    return rightRotation(T1);
}

int getHeight(Tree AVL){
    if(AVL)
        return AVL->height;
    else
        return 0;
}

如果有哪里理解错的地方欢迎大家留言交流,如需转载请标明出处。

如果你没看懂一定是我讲的不好,欢迎留言,我继续努力。

手工码字码代码,如果有帮助到你的话留个赞吧,谢谢。

以上。

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值