PTA-查找-AVL 树的根

目录

题目

题目描述

输入格式:

输出格式:

输入样例1:

输出样例1:

输入样例2:

输出样例2:

代码实现

结果

题解1

题解2

算法核心

题解1代码讲解

创造平衡二叉树

插入新节点

调整

平衡调整

准备 

判断类型

调整结构

更新树高

题解2代码讲解

创建平衡二叉树

插入新节点

平衡调节

左右旋


题目

题目描述

将给定的一系列数字插入初始为空的 AVL 树,请你输出最后生成的 AVL 树的根结点的值。

输入格式:

输入的第一行给出一个正整数 n(≤20),随后一行给出 n 个整形 int 范围内的、不同的整数,其间以空格分隔。

输出格式:

在一行中输出顺序插入上述整数到一棵初始为空的 AVL 树后,该树的根结点的值。

输入样例1:

5
88 70 61 96 120

输出样例1:

70

输入样例2:

7
88 70 61 96 120 90 65

输出样例2:

88

代码实现

结果

题解1

跳过左旋右旋直接调整

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

// 优化构建平衡二叉树

typedef struct tree
{
    int info;
    int h;
    struct tree* lchild;
    struct tree* rchild;
}*ptree, btree;

ptree initnode()
{
    ptree tmp = (ptree)malloc(sizeof(btree));
    if(!tmp)
    { 
        perror("initnode");
        return NULL;
    }
    tmp->lchild = tmp->rchild = NULL;
    tmp->h = 1;
    
    return tmp; 
}

void BalanceAVL(ptree *root)
{
    if(*root == NULL) return;

    int q = ((*root)->lchild?(*root)->lchild->h:0)-((*root)->rchild?(*root)->rchild->h:0);
    if(q >= -1 && q <= 1) return; // 无法调整

    ptree rotateroot = (q>0?(*root)->lchild:(*root)->rchild);
    int qq = (rotateroot->lchild?rotateroot->lchild->h:0)-(rotateroot->rchild?rotateroot->rchild->h:0);

    ptree tmp = rotateroot, t1, t2;
    if(q*qq > 0)
    {
        if(q > 0) // LL
        {
            t1 = tmp->lchild;
            t2 = *root;
        }
        else // RR
        {
            t1 = *root;
            t2 = tmp->rchild;
        }
    }
    else
    {
        if(q > 0) // LR
        {
            t1 = tmp;
            t2 = *root;
            tmp = tmp->rchild;
        }
        else // RL
        {
            t1 = *root;
            t2 = tmp;
            tmp = tmp->lchild;
        }
    }

    if(t1 != tmp->lchild)
    {
        t1->rchild = tmp->lchild;
        tmp->lchild = t1;
    }
    if(t2 != tmp->rchild)
    {
        t2->lchild = tmp->rchild;
        tmp->rchild = t2;
    }
    t1->h = (t1->lchild?t1->lchild->h:0)+1;
    t2->h = (t2->rchild?t2->rchild->h:0)+1;
    tmp->h = (t1->h > t2->h ? t1->h : t2->h) + 1;
    *root = tmp;
}

void CreateAVL(ptree *root, int x)
{
    if(*root == NULL)
    {
        (*root) = initnode();
        (*root)->info = x;
    }
    else if((*root)->info > x)
        CreateAVL(&((*root)->lchild), x);
    else if((*root)->info < x)
        CreateAVL(&((*root)->rchild), x);
    
    int lh = ((*root)->lchild?(*root)->lchild->h:0);
    int rh = ((*root)->rchild?(*root)->rchild->h:0);
    (*root)->h = (lh > rh ? lh : rh) + 1;
    BalanceAVL(root);
}

void print(ptree root)
{
    if(root == NULL) return;

    printf("%d %d\n", root->info, root->h);
    print(root->lchild);
    print(root->rchild);
}

int main()
{
    int n;
    scanf("%d", &n);
    ptree root = NULL;
    while(n--)
    {
        int x;
        scanf("%d", &x);
        CreateAVL(&root, x);
        // BalanceAVL(&root);
        // print(root);
    }
    
    printf("%d", root->info);

    return 0;
}

题解2

使用经典的左旋右旋调整平衡,相比题解1更加直观可读

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

// 优化构建平衡二叉树version4
#define get_height(x) (x?x->h:0)

typedef struct tree
{
    int info;
    int h;
    struct tree* lchild;
    struct tree* rchild;
}*ptree, btree;

ptree initnode()
{
    ptree tmp = (ptree)malloc(sizeof(btree));
    if(!tmp)
    { 
        perror("initnode");
        return NULL;
    }
    tmp->lchild = tmp->rchild = NULL;
    tmp->h = 1;
    
    return tmp; 
}

ptree right_rotate(ptree root)
{
    ptree tmp = root->lchild;
    root->lchild = tmp->rchild;
    tmp->rchild = root;
    root->h = get_height(root->rchild)+1;
    tmp->h = (get_height(tmp->lchild)>get_height(tmp->rchild)?get_height(tmp->lchild):get_height(tmp->rchild))+1;
    return tmp;
}

ptree left_rotate(ptree root)
{
    ptree tmp = root->rchild;
    root->rchild = tmp->lchild;
    tmp->lchild = root;
    root->h = get_height(root->lchild)+1;
    tmp->h = (get_height(tmp->lchild)>get_height(tmp->rchild)?get_height(tmp->lchild):get_height(tmp->rchild))+1;
    return tmp;
}

void CreateAVL(ptree *root, int x)
{
    if(*root == NULL)
    {
        (*root) = initnode();
        (*root)->info = x;
        return;
    }
    else if((*root)->info > x)
        CreateAVL(&((*root)->lchild), x);
    else if((*root)->info < x)
        CreateAVL(&((*root)->rchild), x);
    (*root)->h = (get_height((*root)->lchild)>get_height((*root)->rchild)?get_height((*root)->lchild):get_height((*root)->rchild))+1;
    
    int balance = get_height((*root)->lchild)-get_height((*root)->rchild);
    if(balance > 1 && x < (*root)->lchild->info) *root = right_rotate(*root);
    else if(balance < -1 && x > (*root)->rchild->info) *root = left_rotate(*root);
    else if(balance > 1 && x > (*root)->lchild->info)
    {
        (*root)->lchild = left_rotate((*root)->lchild);
        *root = right_rotate(*root);
    }
    else if(balance < -1 && x < (*root)->rchild->info)
    {
        (*root)->rchild = right_rotate((*root)->rchild);
        *root = left_rotate(*root);
    }
}

void delete(ptree root)
{
    if(root == NULL) return;

    delete(root->lchild);
    delete(root->rchild);
    free(root);
}

int main()
{
    int n;
    scanf("%d", &n);
    ptree root = NULL;
    while(n--)
    {
        int x;
        scanf("%d", &x);
        CreateAVL(&root, x);
    }
    
    printf("%d", root->info);
    delete(root);

    return 0;
}

算法核心

首先,读者需要有平衡二叉树的基本概念,平衡二叉树需要满足每个子树都满足平衡二叉树的条件,即平衡因子在 [-1,1] 的范围中(平衡因子是当前子树当作根节点,左子树的相对高度减去右子树的相对高度)。

每次插入新的节点后进行调整,确保每一次插入后树一定平衡。

经过分析,两种算法的核心思路一致,都是将相对高的树提上去,相对矮的树向下调整。这两个题解的区别在于一个是一步到位,另一个是分解了步骤。

题解1代码讲解

输入代码实现较为简单,不作讲解

创造平衡二叉树

void CreateAVL(ptree *root, int x)
{
    // 使用递归将新的节点插入平衡二叉树中
    if(*root == NULL)
    {
        (*root) = initnode();
        (*root)->info = x;
    }
    else if((*root)->info > x)
        CreateAVL(&((*root)->lchild), x);
    else if((*root)->info < x)
        CreateAVL(&((*root)->rchild), x);
    
    // 计算树高
    int lh = ((*root)->lchild?(*root)->lchild->h:0);
    int rh = ((*root)->rchild?(*root)->rchild->h:0);
    (*root)->h = (lh > rh ? lh : rh) + 1;

    // 调整平衡二叉树(ps:这里无视这个子树是不是平衡的,这样有利于找到最小不平衡子树)
    BalanceAVL(root);
}
插入新节点

首先,先像二叉排序树一样把新的节点插入,如果系统学习过查找的读者肯定知道新的节点一定是叶子节点。就是根据 左子树<根<右子树 (节点的某个数据)的特点,为新的数据找到对应的位置,使用递归的方式找,找到位置的标志就是当前节点为空,我们使用initnode()函数创建一个新的节点,将当前的数据存入这个新节点中,同时利用递归计算插入了新节点后这个节点的父节点以及上面的节点的树高,比较左右子树的树高,取最大值,为调整做准备。

调整

插入的新节点可能会破坏平衡二叉树的性质,所以我们需要维护平衡,这个交给下面的讲解。调整平衡也需要利用递归,一方面是方便找到最小不平衡子树,另一方面是为了防止出现不平衡向上传导,导致无法调整平衡。

平衡调整

void BalanceAVL(ptree *root)
{
    if(*root == NULL) return; // 无需调整

    int q = ((*root)->lchild?(*root)->lchild->h:0)-((*root)->rchild?(*root)->rchild->h:0);
    if(q >= -1 && q <= 1) return; // 无需调整

    ptree rotateroot = (q>0?(*root)->lchild:(*root)->rchild);
    int qq = (rotateroot->lchild?rotateroot->lchild->h:0)-(rotateroot->rchild?rotateroot->rchild->h:0);

    ptree tmp = rotateroot, t1, t2;
    if(q*qq > 0)
    {
        if(q > 0) // LL
        {
            t1 = tmp->lchild;
            t2 = *root;
        }
        else // RR
        {
            t1 = *root;
            t2 = tmp->rchild;
        }
    }
    else
    {
        if(q > 0) // LR
        {
            t1 = tmp;
            t2 = *root;
            tmp = tmp->rchild;
        }
        else // RL
        {
            t1 = *root;
            t2 = tmp;
            tmp = tmp->lchild;
        }
    }

    if(t1 != tmp->lchild)
    {
        t1->rchild = tmp->lchild;
        tmp->lchild = t1;
    }
    if(t2 != tmp->rchild)
    {
        t2->lchild = tmp->rchild;
        tmp->rchild = t2;
    }
    t1->h = (t1->lchild?t1->lchild->h:0)+1;
    t2->h = (t2->rchild?t2->rchild->h:0)+1;
    tmp->h = (t1->h > t2->h ? t1->h : t2->h) + 1;
    *root = tmp;
}
准备 

在调整前我们需要解决调用这个函数时,无论是什么情况都传入,我们做出几个判断来提高调整函数的鲁棒性,当需要调整的树是空树时返回(一般不会发生),当需要调整的树的平衡因子在 [-1, 1] 之间时返回。

在下一步前,先讲解一下树高的设置,为了和空节点(也就是失败节点)区别开,同时比较时也正好能表示这个节点的树高,在创建时设置为1。

判断类型

接下来,我们计算根节点的平衡,使用上面说的条件判断,再根据平衡因子来决定调整的节点在左边还是右边,我们将这个初步确认的需要调整的节点使用一个变量存起来。然后,在小编的苦思冥想下,终于将LL、LR、RR和RL四种情况使用比较少的代码,比较巧妙的方法实现(比直接根据情况写要少了30行代码)。

在下面的代码中,我们使用了一点数学的技巧,优化比较条件,当两个数同号时,相乘结果将大于0,反之小于0。咦,那如果这两个数中有一个是0呢?还记得我们根节点的平衡因子吗?如果这个平衡因子大于1或小于-1,那么初步确认的需要调节节点两个子树的高一定不相等,因为在这个程序中,每一次插入的新节点一定会被调整,如果节点在插入这个新节点前就有了同样树高的兄弟节点,一定会被调整,也就是说像LL型中根的左孩子不可能有右孩子(会被视为LR型调整),一定只有右孩子

同号就说明是LL或RR型,异号就说明是LR或RL型。

再使用t1,t2两个变量记录需要调整的节点调整后的左孩子和右孩子,通过确认调整类型来确认调整后的左孩子和右孩子的节点。

当调节类型为LL型时,左孩子就是初步确认需要调节的节点的左孩子,右孩子就是根。如下图

 以此类推,我们可以确认下来每一个调节类型的t1和t2。

当调节类型为RR型时,左孩子为根,右孩子为需要调节的节点的右孩子。

当调节类型为LR型时,左孩子为需要调节的节点,右孩子为根,需要调节的节点更新为前一个需要调节的节点的右孩子。如下图

当调节类型为RL型时,左孩子为根,右孩子为需要调节的节点,需要调节的节点更新为前一个需要调节的节点的左孩子。

然后判断t1是否为rotateroot的左孩子(LL型t1已经是左孩子,就无需更新),判断t2是否为rotateroot的右孩子(RR型中t2已经是右孩子,就无需更新)。

调整结构

有了变量记录rotateroot更新后的左右孩子,就可以直接对左右孩子进行修改,将左孩子的右孩子改为rotateroot的左孩子,将右孩子的左孩子改为rotateroot的左孩子(此时t1右孩子和t2左孩子都是空的)。再将rotateroot的左孩子修改为t1,rotateroot的右孩子修改为t2,就将结构调整完了。

更新树高

最后更新左右孩子的树高以及调整后的根节点的树高,就完成一次调整了。由于每次都是调整最下面的节点开始,和为什么平衡因子大于1或小于-1时两个子树的树高一定不同类似,LL型没有左孩子的右孩子,也就只需考虑左孩子的左孩子的树高了,也可以这么考虑,根的左孩子的左孩子的树高一定不小于根的左孩子的右孩子的左孩子(可以参考下面的图理解)。所以只需要考虑左孩子的左孩子的树高就可以了(右孩子树高更新同理)。

题解2代码讲解

首先解释#define 这段语句的意思:使用宏函数,将大量出现的求高的三目表达式伪装成函数get_height()(实际上代码量没有减少,在编译阶段会将所有宏定义替换回去)。之所以使用这个语句是为了提高代码的可读性(函数也可以达到一样的效果)。

创建平衡二叉树

void CreateAVL(ptree *root, int x)
{
    if(*root == NULL)
    {
        (*root) = initnode();
        (*root)->info = x;
        return;
    }
    else if((*root)->info > x)
        CreateAVL(&((*root)->lchild), x);
    else if((*root)->info < x)
        CreateAVL(&((*root)->rchild), x);
    (*root)->h = (get_height((*root)->lchild)>get_height((*root)->rchild)?get_height((*root)->lchild):get_height((*root)->rchild))+1;
    
    int balance = get_height((*root)->lchild)-get_height((*root)->rchild);
    if(balance > 1 && x < (*root)->lchild->info) *root = right_rotate(*root);
    else if(balance < -1 && x > (*root)->rchild->info) *root = left_rotate(*root);
    else if(balance > 1 && x > (*root)->lchild->info)
    {
        (*root)->lchild = left_rotate((*root)->lchild);
        *root = right_rotate(*root);
    }
    else if(balance < -1 && x < (*root)->rchild->info)
    {
        (*root)->rchild = right_rotate((*root)->rchild);
        *root = left_rotate(*root);
    }
}
插入新节点

由于和题解1的代码一样,这里就不再赘述

平衡调节

先计算将所有的节点的树高更新出来,从左子树和右子树中取最大的树高,再计算平衡因子,根据平衡因子以及新插入的节点的位置来确定调整类型或者不进行调整。 

如果是LL型,将根右旋;如果是RR型,将根左旋。如下图为LL的一种情况,调整后将树高比较高的子树提了上去,让树高相同的子树在同一层上。(原理小编也不懂)

如果是LR型,就先将t1(节点调节后的左孩子)左旋,再将根右旋;如果是RL型,先将t2(节点调节后的右孩子)右旋,再将根左旋。如下图为LR的一种情况,基本把所有树高相同的节点放在同一层上。

左右旋

ptree right_rotate(ptree root)
{
    ptree tmp = root->lchild;
    root->lchild = tmp->rchild;
    tmp->rchild = root;
    root->h = get_height(root->rchild)+1;
    tmp->h = (get_height(tmp->lchild)>get_height(tmp->rchild)?get_height(tmp->lchild):get_height(tmp->rchild))+1;
    return tmp;
}

ptree left_rotate(ptree root)
{
    ptree tmp = root->rchild;
    root->rchild = tmp->lchild;
    tmp->lchild = root;
    root->h = get_height(root->lchild)+1;
    tmp->h = (get_height(tmp->lchild)>get_height(tmp->rchild)?get_height(tmp->lchild):get_height(tmp->rchild))+1;
    return tmp;
}

两种旋转的方式基本对称,注意要先将根的孩子改为调整上移的节点的孩子,类似链表的插入。更新树高和题解1的更新树高一模一样,不再赘述。

至此,代码就讲解完了,恭喜你学会了如果调节平衡二叉树,仔细看这两个题解,其实本质上是一样的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值