3.二叉树的同构、判断是否同一棵二叉搜索树
二叉树的同构
给定两棵二叉树T1和T2。如果T1可以通过若干次左右孩子互换就变成T2,则称两棵二叉树是“同构”的
现给定两棵树,请判断是否同构
输入格式
输入给出两颗二叉树的信息
- 先在一行中给出该树的结点数
- 随后n行中,第i行对应编号第i个结点,给出该结点中存储的字母、其左孩子结点的编号、右孩子结点的百脑汇
- 如果孩子结点为空,则在相应位置上给出’-’
输入样例
8
A 1 2
B 3 4
C 5 -
D - -
E 6 -
G 7 -
F - -
H - -
8
G - 4
B 7 6
F - -
A 5 1
H - -
C 0 -
D - -
E 2 -
求解思路
- 二叉树表示
- 建二叉树
- 同构判别
二叉树表示
#define MaxTree 10
#define ElementType char
#define Tree int
#define Null -1 //注意区别于cstdlib中的NULL(0),因为0也是下标,所以将“指针”为空的情况设置为-1
结构数组表示二叉树:静态链表 //物理上的存储是使用数组,但运用链表的思想
struct TreeNode //顺序存储实现
{
ElementType Element;
Tree Left;
Tree Right;
}T1[MaxTree],T2[MaxTree]; //T1,T2为结构数组,保存整棵二叉树
程序框架搭建:伪代码描述
int main()
{
建二叉树1
建二叉树2
判别是否同构并输出
return 0;
}
需要设计的函数
- 读数据建立二叉树
- 二叉树同构判别
int main()
{
Tree R1,R2;
R1=BuildTree(T1);
R2=BuildTree(T2);
if(Isomorphic(R1,R2))printf("Yes\n");
else printf("No\n");
return 0;
}
建立二叉树
Tree BuildTree(struct TreeNode T[])
{
int i,N,check[N];
Tree cl,cr,Root;
scanf("%d\n",&N);
if(N)
{
for(i=0;i<N;i++)check[i]=0; //check数组用来判别结点是否有其他结点指向
for(i=0;i<N;i++)
{
scanf("%c %c %c\n",&T[i].Element,&cl,&cr);
if(cl!='-')
{
T[i].Left=cl-'0';
check[T[i].Left]=1;
}
else T[i].Left=Null;
if(cr!='-')
{
T[i].Right=cr-'0';
check[T[i].Right]=1;
}
else T[i].Right=Null;
}
for(i=0;i<N;i++)if(!check[i])break;
Root=i; //T[i]没有任何一个结点的Left(cl)和Right(cr)指向,因此为根结点
}
return Root;
}
判别二叉树同构
int Isomorphic(Tree R1,Tree R2)
{
if((R1==Null)&&(R2==Null))return 1;
//两树(子树)均为空
if(((R1==Null)&&(R2!=Null))||((R1!=Null)&&(R2==Null)))return 0;
//其中一棵树(子树)为空
if(T1[R1].Element!=T2[R2].Element)return 0;
//根不同
if((T1[R1].Left==Null)&&(T2[R2].Left==Null))return Isomorphic(T1[R1].Right,T2[R2].Right);
//都没有左子树
if(((T1[R1].Left!=Null)&&(T2[R2].Left!=Null))&&((T1[T1[R1].Left].Element)==(T2[T2[R2].Left].Element)))
return (Isomorphic(T1[R1].Left,T2[R2].Left)&&Isomorphic(T1[R1].Right,T2[R2].Right));
//左右不需要交换的情况
else return (Isomorphic(T1[R1].Left,T2[R2].Right)&&Isomorphic(T1[R1].Right,T2[R2].Left));
//左右需要交换的情况
}
判断是否同一棵二叉搜索树
给定一个插入序列就可以唯一确定一棵二叉搜索树,然而一棵给定的二叉搜索树却可以有多种不同的插入序列得到
- 如:按照序列{2,1,3}和{2,3,1}插入初始为空的二叉搜索树,都能得到同样的结果
对于输入的各种插入序列,需要判断它们是否能生成相同的二叉搜索树
输入样例
4 2
3 1 4 2
3 4 1 2
3 2 4 1
2 1
2 1
1 2
0
输出样例
YES
NO
NO
求解思路
两个序列是否对应相同二叉搜索树的三种判别方法
- 根据两个序列分别建两颗二叉搜索树的判别方法
- 根据两个序列分别建二叉搜索树,在判别二叉搜索树是否相同
- 不建树的判别方法
- 如:序列3 1 2 4和序列3 4 1 2的构造均为{1 2} 3 {4}
- 序列3 1 2 4和序列3 2 4 1的构造则为{2,1} 3 {4}
- 只建一棵二叉搜索树,再判断其他序列是否与该二叉搜索树一致
考虑使用第三种方法,求解思路为:
- 二叉搜索树表示
- 建二叉搜索树T
- 判别一序列是否与二叉搜索树T一致
二叉搜索树表示
typedef struct TreeNode *Tree;
struct TreeNode
{
Tree Left,Right;
int v,flag;
};
程序框架搭建
int main()
{
读入数据N和L
根据读入第一行输入序列建树T
依据树T分别判断后面的L个序列是否能与T形成同一搜索树并输出结果
}
需要设计的主要函数
- 读数据并建搜索树
- 判别一序列时候与T构成一样的搜索树
int main()
{
int N,L,i;
Tree T;
scanf("%d",&N);
while(N) //当基准序列长度不为0时
{
scanf("%d",&L);
T=MakeTree(N); //建基准二叉树
for(i=0;i<L;i++) //读L个序列,分别进行比较
{
if(Judge(T,N))printf("Yes\n");
else printf("No\n");
ResetT(T); //一个序列比较之后,将T中的标记重置
}
FreeTree(T); //L个序列均比较完成后,释放掉基准树
scanf("%d",&N); //每次循环最后更新基准序列长度,当输入序列长度为0时跳出循环
}
return 0;
}
建立二叉搜索树
Tree MakeTree(int);
Tree Insert(Tree,int);
Tree NewNode(int);
Tree MakeTree(int N)
{
Tree T;
int i,V;
scanf("%d",&V);
T=NewNode(V);
for(i=1;i<N;i++)
{
scanf("%d",&V);
T=Insert(T,V);
}
return T;
}
Tree Insert(Tree T,int V)
{
if(!T)T=NewNode(V);
else if(V<T->v)T->Left=Insert(T->Left,V);
else if(V>T->v)T->Right=Insert(T->Right,V);
return T;
}
Tree NewNode(int V)
{
Tree T=(Tree)malloc(sizeof(struct TreeNode));
T->v=V;
T->Left=T->Right=NULL;
T->flag=0;
return T;
}
判别序列与二叉搜索树是否一致
方法:在树T中依次搜索输入序列中所有的元素
- 如果每次搜索所经过的结点在前面均出现过,则一致
- 否则(存在某次搜索中遇到前面未出现的结点),则不一致
int check(Tree T,int V) //搜索序列的一个元素
{
if(T->flag) //如果flag=1,继续递归搜索
{
if(V<T->v)return check(T->Left,V);
else if(V>T->v)return check(T->Right,V);
else return 0; //这种情况说明数据重复了
}
else //如果flag=0,检查是否是当前输入元素,如果是则结束查找并将元素标记为查找过
{ //如果不是当前元素,则说明序列与基准树不是同一棵二叉搜索树
if(V==T->v)
{
T->flag=1;
return 1;
}
else return 0;
}
return 0;
}
int judge(Tree T,int N) //这个版本有bug:当发现序列中某个数与T不一致,judge函数会直接返回,这轮输入序列的读取就停止了
{ //而这一轮输入序列剩下的数会被当作是下一轮的输入,导致程序会出错
int i,V;
scanf("%d",&V); //先判断一下根
if(V!=T->v)return 0;
else T->flag=1;
for(i=1;i<N;i++)
{
scanf("%d",&V);
if(!check(T,V))return 0;//依次检查输入序列的元素,
}
return 1;
}
int judge(Tree T,int N) //更正版本
{
int i,V,flag=0; //flag=0代表目前序列与基准树还一致,1代表已经不一致
scanf("%d",&V);
if(V!=T->v)flag=1;
else T->flag=1;
for(i=1;i<N;i++)
{
scanf("%d",&V);
if(!check(T,V)&&(!flag))flag=1; //此时发现序列与基准情况不一致,程序唯一的工作就是将输入读完
}
if(flag)return 0;
return 1;
}
清除T中各结点的flag标记,准备比较下一个输入序列
- 与遍历原理相同,需要注意的是要对结点进行操作,所以不能遍历到结点为空再停止
void ResetT(Tree T)
{
if(T->Left)ResetT(T->Left);
else if(T->Right)ResetT(T->Right);
T->flag=0;
}
释放T的空间
void FreeTree(Tree T)
{
if(T->Left)ResetT(T->Left);
else if(T->Right)ResetT(T->Right);
free(T);
}
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 hetumessi's Blog!
评论
ValineGitalk