PTA中国大学MOOC-陈越、何钦铭-数据结构-2017春课后作业03树1 树的同构

03-树1 树的同构   (25分)

给定两棵树T1和T2。如果T1可以通过若干次左右孩子互换就变成T2,则我们称两棵树是“同构”的。例如图1给出的两棵树就是同构的,因为我们把其中一棵树的结点A、B、G的左右孩子互换后,就得到另外一棵树。而图2就不是同构的。


图1


图2

现给定两棵树,请你判断它们是否是同构的。

输入格式:

输入给出2棵二叉树树的信息。对于每棵树,首先在一行中给出一个非负整数NNN (≤10\le 1010),即该树的结点数(此时假设结点从0到N−1N-1N1编号);随后NNN行,第iii行对应编号第iii个结点,给出该结点中存储的1个英文大写字母、其左孩子结点的编号、右孩子结点的编号。如果孩子结点为空,则在相应位置上给出“-”。给出的数据间用一个空格分隔。注意:题目保证每个结点中存储的字母是不同的。

输出格式:

如果两棵树是同构的,输出“Yes”,否则输出“No”。

输入样例1(对应图1):

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 -

输出样例1:

Yes

输入样例2(对应图2):

8
B 5 7
F - -
A 0 3
C 6 -
H - -
D - -
G 4 -
E 1 -
8
D 6 -
B 5 -
E - -
H - -
C 0 2
G - 3
F - -
A 1 4

输出样例2:

No




思路简要分析:怎么找根节点就不详细介绍了,有看课程的同学应该都知道,并不是很难,这个题目最大的难点就是如果弄清楚这棵树的左右节点到底换没换过,这个是最重要的,
因为题目并没有要求树的左右孩子都进行交换,只要一部分节点交换一部分节点没交换都算是同构,这里是我个人认为最最麻烦的地方。因为这个特性,我们无法用遍历直接
判断,如果是所有孩子都必须换的话,那我们用一个遍历即可判断,但是并不是所有孩子都需要换的,所以这个问题最大的难点就在这里,我觉得。
还有一个重点,判断两颗树是否同构,即判断两棵树的孩子是否相同,所以只要满足return((树1左==树2左 && 树1右==树2右) || (树1左==树2右 && 树1右 ==树2 左))
两种情况只要满足一种就行了,所以我们如果确定了其中一种情况肯定不满足那么只要考虑第二张种情况就够了,因为||的特性。
然后在确定两棵树里每棵树最少有一个空节点的时候,只要考虑另外两个个节点就够了。
下面附上代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<math.h>
#define Null -1
#define MAX 10
struct tree
{
	char name;
	int left;
	int right;
} tree1[MAX],tree2[MAX];;
int find(struct tree *tree,int n);
int compare (int t1,int t2);
int main()
{
	
	int n;
	scanf("%d",&n);
	char wgzf;
	scanf("%c",&wgzf);
	int i;
	for(i=0;i<n;i++)
	{
		char inf[7];
		gets(inf);
		tree1[i].name=inf[0];
		if(inf[2]!='-')
			tree1[i].left=inf[2]-48;
		else
			tree1[i].left=Null;
		if(inf[4]!='-')
			tree1[i].right=inf[4]-48;
		else
			tree1[i].right=Null;
	}
	int k;
	scanf("%d",&k); 
	scanf("%c",&wgzf);
	for(i=0;i<k;i++)
	{
		char inf[7];
		gets(inf);
		tree2[i].name=inf[0];
		if(inf[2]!='-')
			tree2[i].left=inf[2]-48;
		else
			tree2[i].left=Null;
		if(inf[4]!='-')
			tree2[i].right=inf[4]-48;
		else
			tree2[i].right=Null;
	}
	if(compare(find(tree1,n),find(tree2,n)))
		printf("Yes");
	else
		printf("No");
    return 0;
} 
int find(struct tree *tree,int n)
{
	int symbol[10]={0};
	int i;
	for(i=0;i<n;i++)
	{
		if( tree[i].left!=-1 )
			symbol[ tree[i].left ]=1;
		if( tree[i].right!=-1 )
			symbol[ tree[i].right ]=1;
	}
	for(i=0;i<n;i++)
		if(symbol[i]==0)
			return i;
	return -1;
}
int compare (int t1,int t2)        //一个可以判断两个树根是否同构的函数 
{
	if(t1==-1 && t2 == -1)			//同为空树,因此我们认为相同 
		return 1;
	if(t1==-1 && t2!=-1 || t1!=-1 && t2== -1)	//一空一不空,返回0. 
		return 0;
	if(tree1[t1].name != tree2[t2].name)		//根都不空但是不相同,返回0. 
		return 0; 
		//接下来是在根不空且相同之下的判别 
	if(tree1[t1].left == -1 && tree2[t2].left == -1)	//两棵树最多都只有一个节点,不存在对应关系,分析剩下的节点即可,如果硬要分析,
//因为如果没转,那么情况2必定不满足,考虑情况1,又因为左子树已相同,所以只需考虑右子树,判别。如果转了,那么情况一必定不满足,考虑情况2,因为如果同构,那么
//右树必定相等,因为左树对应右树,右树对应左树,左树已经相等,那么右树必定相等,所以依旧只要考虑右树是否相等即可判别。
 return(compare(tree1[t1].right,tree2[t2].right));
	 if(tree1[t1].left != -1 && tree2[t2].left!= -1 && tree1[tree1[t1].left].name==tree2[tree2[t2].left].name) //两树左子树不空且相同,既然两棵树的左子树相同了,
		return (compare(tree1[t1].left,tree2[t2].left) && compare(tree1[t1].right,tree2[t2].right));//那1树左子树肯定不等于2树右子树,因为相同的节点只能有一个父节点,所以必定是左左比较右右比较 
	else//这里有三种情况,1:树1左空树2左不空。2:树1左不空树2左空。3:树1树2左字树不空但不相等。这三种情况都只对应了一种可能,树 1左子树对应数2右子树,因为树1左子树对应树2左子树已经不可能了。 
		return (compare(tree1[t1].left,tree2[t2].right)&& compare(tree1[t1].right,tree2[t2].left));
} 
前面的代码都可以粗略的看一下,重点就是这最后一个函数的代码。
如何判断是否同构,或者说,如何判断到底是树1的左子树对应着树2的左子树还是树1的左子树对应着树2的右子树,这很重要,也是重点所在


首先前面三个判别条件是判断根相不相同的,这里不给予详细的解释,如果三个判别条件都不满足,那么只说明了一件事,两棵树的根都存在并且相同。
接下来才有资格进行接下来的判别,判别树1与树2 的对应关系,左右孩子的对应:
首先判别两棵树的左子树是不是都为空,其实判别两颗树的右子树也是一样的,如果判别的是右子树,那么return 必须是左子树的判别,而且下面的if也要改成右子树的判别
如果左子树都为空子树,那么有四种情况,一:树1右子树空树2右子树不空。二:树1右子树不空树2右子树空。三:树一树二右子树都空。四:树1树2右子树都不空。
虽然有四种情况,但是我们还是只要返回右子树的判别就够了,因为情况一和情况二下跳到下一个函数时会返回0,本来也确实是0,因为两棵树一颗有一个子树一颗没有子树
那么肯定不同构,情况三跳到下一个函数的时候会返回1,都没有左右子树且跟节点相同嘛,说明同构,情况四的话说明两棵树都只有一个孩子,不管树的左右孩子有没有转换
,都无所谓,只要判别他们有的孩子同构不同构就行了,因为这里只有一个孩子,所以不存在两棵树的左右对应关系。只有当两棵树的两个孩子都存在的时候还会出现左右对应
的关系,比如如果树1转换过左右孩子得到的数2的话,树1的左孩子就对应着树2的右孩子,树1的右孩子就对应着树2的左孩子,没转换过的话就是左孩子对应左孩子,右孩子
对应右孩子。
如果前四个判别条件都不满足,那么两种情况,树1树2里只有其中一棵树有左孩子或者树1树2都有左孩子,那么接下来该如何判别呢?
先考虑都有左孩子的情况,如果左孩子节点相同,因为相同的节点只有一个,一棵树的左节点不可能同时和另一棵树的左右节点都相等,
所以我们发现,如果他们同构的话,那么肯定是左孩子对应左孩子,右孩子对应右孩子,2情况必定是返回0,所以再递归调用判别1情况即可。

本函数主要是要分析出左右孩子的对应关系,一旦分析出来,题目就好做了。

如果不满足最后一个if条件,那么很明显,两棵树的左子树必定不相等,那么1情况返回的必然是0。如果他们是同构的,那么必须是树1左子树对应树2右子树,树2左子树对应
树1右子树。只能是这个对应2情况的对应关系,再递归调用即可。

又因为我思路简要分析的时候提出情况,所以代码可以改成,这样,方便理解,但是程序运行起来会复杂一点
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<math.h>
#define Null -1
#define MAX 10
struct tree
{
	char name;
	int left;
	int right;
} tree1[MAX],tree2[MAX];;
int find(struct tree *tree,int n);
int compare (int t1,int t2);
int main()
{
	
	int n;
	scanf("%d",&n);
	char wgzf;
	scanf("%c",&wgzf);
	int i;
	for(i=0;i<n;i++)
	{
		char inf[7];
		gets(inf);
		tree1[i].name=inf[0];
		if(inf[2]!='-')
			tree1[i].left=inf[2]-48;
		else
			tree1[i].left=Null;
		if(inf[4]!='-')
			tree1[i].right=inf[4]-48;
		else
			tree1[i].right=Null;
	}
	int k;
	scanf("%d",&k); 
	scanf("%c",&wgzf);
	for(i=0;i<k;i++)
	{
		char inf[7];
		gets(inf);
		tree2[i].name=inf[0];
		if(inf[2]!='-')
			tree2[i].left=inf[2]-48;
		else
			tree2[i].left=Null;
		if(inf[4]!='-')
			tree2[i].right=inf[4]-48;
		else
			tree2[i].right=Null;
	}
	if(compare(find(tree1,n),find(tree2,n)))
		printf("Yes");
	else
		printf("No");
    return 0;
} 
int find(struct tree *tree,int n)
{
	int symbol[10]={0};
	int i;
	for(i=0;i<n;i++)
	{
		if( tree[i].left!=-1 )
			symbol[ tree[i].left ]=1;
		if( tree[i].right!=-1 )
			symbol[ tree[i].right ]=1;
	}
	for(i=0;i<n;i++)
		if(symbol[i]==0)
			return i;
	return -1;
}
int compare (int t1,int t2)        //一个可以判断两个树根是否相同的函数 
{
	if(t1==-1 && t2 == -1)			//同为空树,因此我们认为相同 
		return 1;
	if(t1==-1 && t2!=-1 || t1!=-1 && t2== -1)	//一空一不空,返回0. 
		return 0;
	if(tree1[t1].name != tree2[t2].name)		//根都不空但是不相同,返回0. 
		return 0; 
		//接下来是在根不空且相同之下的判别 
	return (compare(tree1[t1].left,tree2[t2].left)&&compare(tree1[t1].right,tree2[t2].right) ||	compare(tree1[t1].right,tree2[t2].left) &&compare(tree1[t1].left,tree2[t2].right));
} 


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值