题意:给一个N以及序列(不用考虑元素重复情况)。然后根据这个建AVL树,输出AVL树的层序遍历,以及判断此树是否是完全二叉树。
难点:这道题没有什么思路。题目很清晰,考点就是AVL建树+层次遍历+完全二叉树的判断。自己下手的时候才意识到AVL树怎么写来着。。。就去记了一下。。。;层次遍历很简单的,就不多提了;如何判断是否是完全二叉树呢?对于完全二叉树而言,有一个节点没有孩子了,那之后的节点(层次遍历顺序上)肯定都没有孩子了!写代码的时候,别忘了,按照先左后右的顺序。而右子树里面的条件左子树里别忘了写。。。忘了就直接一个点过不了,一个点8分呀,血亏!
参考博客有:
1、柳神的写法——https://blog.youkuaiyun.com/liuchuo/article/details/53561924
2、另外两位博主,一位有图可以用来思考——https://blog.youkuaiyun.com/u014634338/article/details/42465089
另一位有不错的代码总结表格,我也用来借鉴——https://blog.youkuaiyun.com/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;
}