如有不对,不吝赐教
下面进入正题:
对于二叉搜索树,我们规定任一结点的左子树仅包含严格小于该结点的键值,而其右子树包含大于或等于该结点的键值。如果我们交换每个节点的左子树和右子树,得到的树叫做镜像二叉搜索树。
现在我们给出一个整数键值序列,请编写程序判断该序列是否为某棵二叉搜索树或某镜像二叉搜索树的前序遍历序列,如果是,则输出对应二叉树的后序遍历序列。
输入格式:
输入的第一行包含一个正整数N(≤1000),第二行包含N个整数,为给出的整数键值序列,数字间以空格分隔。
输出格式:
输出的第一行首先给出判断结果,如果输入的序列是某棵二叉搜索树或某镜像二叉搜索树的前序遍历序列,则输出YES,否侧输出NO。如果判断结果是YES,下一行输出对应二叉树的后序遍历序列。数字间以空格分隔,但行尾不能有多余的空格。
输入样例1:
7
8 6 5 7 10 8 11
输出样例1:
YES
5 7 6 8 11 10 8
输入样例2:
7
8 6 8 5 10 9 11
输出样例2:
NO
这道题是对二叉搜索树或者其镜像树的判断,一种很容易想到的方法就是自己按照它的数据递归地来建一棵树,然后在来一遍后序遍历,我在这里给一种直接使用先序遍历来得到后序遍历以及判断的代码。
下面先给出代码,然后在逐条分析:
#include<stdio.h>
int pos; //填充的位置
int mirror; //是否是镜像树
int lastroot=-1; //表示上一个根节点
int Judge(int *postOrder,int *data,int left,int right,int branch);
//branch表示该子树是上一个节点的哪个个分支 初始节点为-1 左子树为0 右子树为1
int main(void)
{
int N,i;
int flag; //是二叉搜索树或者镜像二叉搜索树
scanf("%d",&N);
int data[N];
int postOrder[N];
pos=N-1;
for(i=0;i<N;i++)
scanf("%d",data+i); //输入数据
if(N>1){
if(data[0]<data[1])
mirror=1; //说明是镜像树
flag=Judge(postOrder,data,0,N-1,-1);
}
else{
flag=1;
postOrder[0]=data[0];
} //只有一个数据
if(flag){
printf("YES\n");
printf("%d",postOrder[0]);
for(i=1;i<N;i++)
printf(" %d",postOrder[i]);
}
else
printf("NO\n");
return 0;
}
int Judge(int *postOrder,int *data,int left,int right,int branch)
{
if(left>right)
return 1;
postOrder[pos--]=data[left]; //倒置根节点
if(left==right)
return 1;
int flag=1;
int i;
int root; //检索的根节点
int mid; //分隔两个序列的下标
root=left;
i=root+1;
if(!branch){
if(!mirror){
while(data[i]<data[lastroot]&&i<=right)
i++;
} //非镜像树
else{
while(data[i]>=data[lastroot]&&i<=right)
i++;
}
} //左子树的判断
else if(1==branch){
if(!mirror){
while(data[i]>=data[lastroot]&&i<=right)
i++;
}
else{
while(data[i]<data[lastroot]&&i<=right)
i++;
}
} //右子树的判断
if(-1!=branch&&i!=right+1)
return 0;
i=root+1;
if(!mirror){ //说明是二叉树
while(i<=right&&data[root]>data[i])
i++;
mid=i;
if(flag){
while(i<=right&&data[root]<=data[i])
i++;
}
}
else{ //说明是镜像二叉树
while(i<=right&&data[root]<=data[i])
i++;
mid=i;
if(flag){
while(i<=right&&data[root]>data[i])
i++;
}
}
if(i==right+1){
lastroot=root;
flag=Judge(postOrder,data,mid,right,1);
lastroot=root;
flag=flag&Judge(postOrder,data,left+1,mid-1,0);
}
else
flag=0;
return flag;
}
//满足条件的树的先序遍历必定满足下面条件
//除根节点外 后面的序列分为小于根节点和大于等于根节点的连续序列
1.几个变量的说明:
int pos; //填充的位置
int mirror; //是否是镜像树
int lastroot=-1; //表示上一个根节点
int Judge(int *postOrder,int *data,int left,int right,int branch);
//branch表示该子树是上一个节点的哪个个分支 初始节点为-1 左子树为0 右子树为1
pos表示的是我们在后序遍历中下一个填入元素的位置,因为我是直接使用由前序填后序的方法。
lastroot表示该上一轮判断中的根节点,我使用递归写的,在Judge判断左子树和右子
树,lastroot就是此时的根节点,在我们判断一个树是否是二叉树时有很大的作用。
Judge中的参数:
postOrder是后序遍历的数组,data是题目给的先序遍历,left和right分别是读取数据的左右区间(待会分析方法的时候解释),branch表示的是当前检验的分支是上一个节点的左子树还是右子树,第一个根节点就直接-1。
2.如何由先序遍历直接得到后序遍历:
先序遍历的特点就是:先搞根节点,然后递归左子树,在然后右子树。
那么给出的区间(区间[left,right])中肯定分为三段:根节点(第一个元素),左子树的一段,右子树的一段。(其中因为这道题存在镜像树,所以左右子树的顺序是可以交换的)。
而后序节点就是:先搞左子树,然后右子树,最后根节点。
我们就可以利用这两者的性质来做:
先把根节点放到最后,然后递归处理左子树和右子树。这样就OK了。
注意的一点就是我们这里把左子树和右子树有区间来表示。
那么现在的问题就是如何把这两个区间分割出来了,注意到二叉搜索树的特点就是左子树上的所有值一定比根节点小,右子树上的所有值一定大于等于根节点,我们就可以按照这个连续性来分隔:
if(!mirror){ //说明是二叉树
while(i<=right&&data[root]>data[i])
i++;
mid=i;
if(flag){
while(i<=right&&data[root]<=data[i])
i++;
}
}
else{ //说明是镜像二叉树
while(i<=right&&data[root]<=data[i])
i++;
mid=i;
if(flag){
while(i<=right&&data[root]>data[i])
i++;
}
}
这里注意一下镜像树,mirror可以根据data[0]和 data[1]的关系来判断:
if(data[0]<data[1])
mirror=1; //说明是镜像树
然后下面就是对整棵树来判断是不是二叉搜索树了,这里会出现这种情况:
任何一个子树都满足,但是不满足左子树/右子树和根节点的关系。
这种情况画个图就是这样(数据为:8 6 7 5 10 11 9):
我们发现5在6的右边,但是5<6,所以这不是一棵二叉搜索树,而这种情况可以用下面代码排除:
if(!branch){
if(!mirror){
while(data[i]<data[lastroot]&&i<=right)
i++;
} //非镜像树
else{
while(data[i]>=data[lastroot]&&i<=right)
i++;
}
} //左子树的判断
else if(1==branch){
if(!mirror){
while(data[i]>=data[lastroot]&&i<=right)
i++;
}
else{
while(data[i]<data[lastroot]&&i<=right)
i++;
}
} //右子树的判断
if(-1!=branch&&i!=right+1)
return 0;
我们将爷爷节点保留下来,然后分条件把其余的比较就行。
3.下面给一些数据(来自这位老哥的:忘记过去的人,必将重蹈覆辙,感谢这位老哥的数据)
下面给出结果: