爆刷PAT(甲级)——之【1123】 Is It a Complete AVL Tree (30 分)——AVL树建树+层次遍历+完全二叉树的判断

题意:给一个N以及序列(不用考虑元素重复情况)。然后根据这个建AVL树,输出AVL树的层序遍历,以及判断此树是否是完全二叉树。

难点:这道题没有什么思路。题目很清晰,考点就是AVL建树+层次遍历+完全二叉树的判断。自己下手的时候才意识到AVL树怎么写来着。。。就去记了一下。。。;层次遍历很简单的,就不多提了;如何判断是否是完全二叉树呢?对于完全二叉树而言,有一个节点没有孩子了,那之后的节点(层次遍历顺序上)肯定都没有孩子了!写代码的时候,别忘了,按照先左后右的顺序。而右子树里面的条件左子树里别忘了写。。。忘了就直接一个点过不了,一个点8分呀,血亏!

 

参考博客有:

     1、柳神的写法——https://blog.csdn.net/liuchuo/article/details/53561924

     2、另外两位博主,一位有图可以用来思考——https://blog.csdn.net/u014634338/article/details/42465089

          另一位有不错的代码总结表格,我也用来借鉴——https://blog.csdn.net/whucyl/article/details/17289841

 

AVL建树的思考:

1、看了柳神的代码以及别人的代码,在节点的高度计算的方面主要是分两个写法——每个节点的h是用的时候迭代算还是在AVL发生旋转更新的时候算出来并保存为节点的一个参数。

我比较喜欢简洁清晰的思维方式以及代码书写方式,但是不管哪个思路和写法都差不多。。都是迭代更新。。。

对比了一下代码量,emmm还不如直接用的时候迭代算呢。所以代码是仿照柳神的代码写的。

2、AVL树的旋转上,不管是哪种旋转,插入的位置和发生变动的根root并不是父子关系,而是爷孙关系!(高度差2!)这点比较重要,整个AVL树的旋转过程,我觉得可以理解成“新王交接,父承子业”的感觉

举个栗子——以左旋和右旋来说,root节点是原来的老皇帝,它的儿子节点son是即将把它赶下去自己上的新皇帝,而我们插入的节点位置是新皇帝的孩子位置!而发生旋转后呢,新王变成root,而原来的旧王(旧的root)就待在原来新王的位置(它还没旋转前的位置),接受了新王原来的孩子(也就是我们插入的节点)

   插入在root节点的左子树的左侧,就是右旋咯,我写的函数是   ll();(就是左子树的左子树)

   插入在root节点的左子树的右侧,就和这个名字一样了,先左旋再右旋咯,我写的函数是   lr();(就是左子树的右子树)

   插入在root节点的右子树的右侧,就是左旋咯,我写的函数是   rr();(就是右子树的右子树)

   插入在root节点的右子树的左侧,就是先右旋再左旋咯,我写的函数是   rl();(就是右子树的右子树) 

在写AVL建树过程中,一定要搞清楚传入的参数root是旋转变换中的哪个节点(答案:旧王~是插入节点的爷爷——爸爸的爸爸

3、为什么大家写的AVL建树函数insert上都有一个步骤——root->left=insert(root->left)或者是root->right=nsert(root->right)呢?可以不按照这个迭代的写法,删掉这句吗?

我试了一下,并不行,这个树就建错了。

仔细想了一下,这是为什么呢?

是因为,如果我们插入了一个新的节点,没发生旋转那就万事OK,但如果新的节点会引起旋转,那也并不是在插入位置进行旋转呀!我们上面讲了,要旋转也是插入点的父亲(新王)和父亲的父亲(旧王)进行旋转呀引入迭代一个是可以更好的进行旋转结点指针的使用指派,带来优化;另一个是新王和旧王的交替,会导致旧王的父亲(插入节点的曾爷爷)的右指针需要更改维护!好好品味一下~所以,insert中需要迭代更新的步骤,是为了使得旋转后的节点能够更新到更高层的树上,不更新这个树就断了

 

Code:

代码的核心就是AVL怎么建树的。

以及如何判断是否是完全二叉树部分。

 

#include<bits/stdc++.h>
#include<unordered_map>
using namespace std;
#define inf 209
#define INF 0x3f3f3f3f
#define loop(x,y,z) for(x=y;x<z;x++)
int n;
struct Node
{
    int value;
    Node *left,*right;
    Node(int v)
    {
        value=v;
        left=right=NULL;
    }
}*root=NULL;

//左旋和右旋的灵魂就是——新王交接,父承子业
//转一次的root,就是旧王,新王是他儿子
//转两次的root,他的儿子是转第一次的旧王,root自己是转第二次的旧王
//ll中左子无敌,rr中右子无敌
//lr中左子必死;rl中右子必死


Node* ll(Node *root)//插在左子树左边,是右旋——root是旧王,左子新王
{
    Node *son=root->left;//造反的新王
    root->left=son->right;//旧王贬官,交接
    son->right=root;//新王摸狗头
    return son;//返回新王
}
Node* rr(Node *root)//插在右子树右边,是左旋——root是旧王,右子新王
{
    //同上噢
    Node *son=root->right;
    root->right=son->left;
    son->left=root;
    return son;
}

//插在左子树的右边,是先左旋再右旋
//则root为旧王,但是root的左子却是左旋的旧王
Node* lr(Node *root)
{
    root->left=rr(root->left);//先左旋
    return ll(root);//再右旋

}

//插在右子树的左边,是先右旋再左旋
//则root为旧王,但是root的右子却是右旋的旧王
Node *rl(Node *root)
{
    root->right=ll(root->right);
    return rr(root);
}

//1、因为AVL树在插入过程中会进行旋转,
//所以对于任何root而言,其左右子树都可能发生旋转,并使得root的左右孩子更改
//所以要左右孩子要保持迭代更新
//2、旋转过程中,判断是否发生旋转,就是在高度差为2的root和root的孩子上,要求高
//综上所述要按照递归写法
int getH(Node *root)//得到节点高
{
    if(!root)return 0;
    int l=getH(root->left);
    int r=getH(root->right);
    return max(l,r)+1;
}
Node* insert(Node *root,int value)
{
    if(!root)root=new Node(value);
    else if(value<root->value)//左边
    {
        root->left=insert(root->left,value);//防止孩子旋转,物是人非
        if(getH(root->left)-getH(root->right)==2)//因为插在左子树,所以要旋转也是左子树变长了
        {
            if(value<root->left->value)//在左子树的左子树上,是ll
                root=ll(root);//新的王
            else
                root=lr(root);
        }

    }
    else //右边
    {
        root->right=insert(root->right,value);//防止孩子旋转,物是人非
        if(getH(root->right)-getH(root->left)==2)//因为插在右子树,所以要旋转也是右子树变长了
        {
            if(value>root->right->value)//在右子树的右子树上,是rr
                root=rr(root);//新的王
            else
                root=rl(root);
        }

    }
    return root;
}

//vector<int>ans;
bool isComplete(Node *root)
{
    queue<Node*>q;
    while(!q.empty())q.pop();
    q.push(root);
    bool flag1=true;
    bool flag2=true;
    int num=0;
    while(!q.empty())
    {
        Node *t=q.front();
        q.pop();
        //输出
        num++;
        if(num==n)printf("%d\n",t->value);
        else printf("%d ",t->value);

        if(t->left)
        {
            q.push(t->left);
            if(!flag1)flag2=false;//不要漏了
        }
        else flag1=false;
        if(t->right)
        {
            q.push(t->right);
            if(!flag1)flag2=false;//不要漏了
        }
        else flag1=false;
    }
    return flag2;
}

int main()
{
    int i,j;
    cin>>n;
    loop(i,0,n)
    {
        cin>>j;
        root=insert(root,j);
    }
    if(isComplete(root))
        printf("YES\n");
    else printf("NO\n");

    return 0;
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值