ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

7-3 树的同构 (25 分) - C语言

2021-11-09 22:59:16  阅读:161  来源: 互联网

标签:25 同构 right return C语言 pb pa 节点 left


7-3 树的同构

时间:2021年11月9日
作者:返祖猿
题目集:数据结构与算法题目集(中文)
题目:7-3 树的同构 (25 分)

1、前言

我居然解出一道树的题!!!

我很激动!!!

我准备趁热打铁把想法记录在这儿,如有雷同纯属偶然。

2、题目

题目与输入输出格式

输入样例 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

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

3、代码

#include<stdio.h>
typedef struct node{
	int id;
	char c;
	struct node *left;
	struct node *right;
} P;
int countChildren(P *pa);
int answer(P *pa, P *pb);
int main(){
	
	// 定义变量 
	P pa[10], pb[10];
	P *rootA, *rootB;
	int flagA[10] = {}, flagB[10] = {};
	int m, n, i;
	char temp;
	
	// 控制输入 1 
	scanf("%d", &n);
	for(i=0; i<n; i++){
		pa[i].id = i;
		// 左节点 
		scanf("\n%c %c ", &pa[i].c, &temp);
		if(isdigit(temp)){
			pa[i].left = &pa[temp-'0'];
			flagA[temp-'0'] = 1;
		} else {
			pa[i].left = NULL;
		}
		// 右节点 
		scanf("%c", &temp);
		if(isdigit(temp)){
			pa[i].right = &pa[temp-'0'];
			flagA[temp-'0'] = 1;
		} else {
			pa[i].right = NULL;
		}
	}
	
	// 控制输入 2 
	scanf("%d", &m);
	for(i=0; i<m; i++){
		pb[i].id = i;
		// 左节点 
		scanf("\n%c %c ", &pb[i].c, &temp);
		if(isdigit(temp)){
			pb[i].left = &pb[temp-'0'];
			flagB[temp-'0'] = 1;
		} else {
			pb[i].left = NULL;
		}
		// 右节点 
		scanf("%c", &temp);
		if(isdigit(temp)){
			pb[i].right = &pb[temp-'0'];
			flagB[temp-'0'] = 1;
		} else {
			pb[i].right = NULL;
		}
	}
	
	// ----------------------------------------------------------------
	// 排除显而易见的不同构树 
	if(n != m){
		printf("No");
		return 0;
	}
	if(n == 1){
		if(pa[0].c != pb[0].c){
			printf("No");
		} else {
			printf("Yes");
		}
		return 0;
	}
	if(n == 0){
		printf("Yes");
		return 0;
	}
	
	// 寻找两棵树的根节点 
	for(i=0; i<n; i++){
		if(flagA[i] == 0){
			rootA = &pa[i];
		}
		if(flagB[i] == 0){
			rootB = &pb[i];
		}
	}
	
	// test: 测试根节点是否正确 
//	printf("rootA: %d %c\n", rootA->id, rootA->c);
//	printf("rootB: %d %c\n", rootB->id, rootB->c);
	// true
	
	// 判断根节点是否相同 
	if(rootA->c != rootB->c){
		printf("No");
		return 0;
	}
	
	if(answer(rootA, rootB) == 0){
		printf("No");
	} else {
		printf("Yes");
	}
	return 0;
}

// 此方法判断节点有几个孩子 
int countChildren(P *pa){
	int count = 2;
	if(pa->left == NULL) count--;
	if(pa->right == NULL) count--;
	
	if(count == 2){
		return 2;
	} else if(count == 0){
		return 0;
	} else if(pa->left != NULL){
		return -1;
	} else if(pa->right != NULL){
		return 1;
	}
}

// 此方法递归判断两棵树的各节点的孩子是否相同 
int answer(P *pa, P *pb){
	
	int countA = countChildren(pa);
	int countB = countChildren(pb);
	
	P *tempA, *tempB;
	
	char pal, par, pbl, pbr;
	
	// 孩子数不同则不同构 
	if(abs(countA) != abs(countB)){
		return 0;
	}
	
	// 全是叶子节点的情况,因为送进来之前已经判断了两个节点相同,所以返回 1 
	if(countA == 0 && countB == 0){
		return 1;
	}
	
	// 都只有一个孩子的情况,这两个孩子保存的字符必须相同 
	if(countA != 2){
		
		if(countA>0){
			tempA = pa->right;
		} else {
			tempA = pa->left;
		}
		if(countB>0){
			tempB = pb->right;
		} else {
			tempB = pb->left;
		}
		
//		printf("\n%c %c\n", tempA->c, tempB->c);
		// note: return 的话程序就结束了,所以不会从这里进入两个孩子的情况 
		if(tempA->c == tempB->c){
			return answer(tempA, tempB);
		} else {
			return 0;
		}
	} 
	
	// 两个孩子的情况 
	pal = pa->left->c;
	par = pa->right->c;
	pbl = pb->left->c;
	pbr = pb->right->c;
//	printf("\n%c %c %c %c\n", pal, par, pbl, pbr);
	// note: 应该没有左右孩子相同的情况,吧…… 
	if((pal == pbl || pal == pbr)&&(par == pbl || par == pbr)){
		if(pal == pbl){
			return answer(pa->left, pb->left) && answer(pa->right, pb->right);
		} else {
			return answer(pa->left, pb->right) && answer(pa->right, pb->left);
		}
	} else {
		return 0;
	}
}

嗐,我自己都觉得这代码太长,真的有人看么 (←_←)

4、思考过程

① 寻找根节点

先不管其他的,读完输入后发现它的 根节点 都不按顺序给出来,着实劝退……

然后注意到根节点一定不会在每行输入的后两个数字中出现,于是用了一个很笨的方法寻找根节点:把输入的后两列数字记录,没有出现的那个数字就是根节点。

事实证明这个方法很好用~

② 实现检查同构

实现输入后开始思考如何实现判断两棵树同构的方法。

a. 寻找检查两个节点的两个孩子相同但顺序不同的方法

同构的最大特点就是两棵树同一节点的两个孩子相同,但顺序不同,然而一个节点有两个孩子,判断两个节点的两个孩子是否一样,还是稍微有点绕的。

  • 先在纸上写出了四个变量:pal, par, pbl, pbr,分别表示四个孩子;
  • 然后推出两个节点的两个孩子相同的判别式:(pal == pbl || pal == pbr) && (par == pbl || par == pbr);
  • 满足判别式的两个节点进入下一场递归:
    • if(pal == pbl) return answer(pa->left, pb->left) && answer(pa->right, pb->right);
    • else return answer(pa->left, pb->right) && answer(pa->right, pb->left);

此处解释一下我为什么要用递归呢,因为用 while 我不会写……咳,真的,有时会觉得递归比 while 还简单,这肯定不是我一个人的想法。

但是实现递归的时候就不会这么想了。

另外,也想过某个字节点的两个孩子里放的字符一样的情况,但从常识上考虑出题人应该不会如此疯狂……吧??姑且还是忽略了。

事实证明确实想太多,疯狂的只有我而已。

b. 排除显而易见的错误答案

当继续向下写的时候就要开始考虑答题了。

再读一遍题,我发现题目没有明说两棵树的节点不可能为 0,于是又仔细看了一遍,发现题目真的没有明说两棵树的节点不为 0,emmm,姑且写上了输出 “No”,提交一遍后发现专门有个测试点专门针对这种情况,而答案是 “Yes”,我竟无言以对。

当节点只有一个的时候,题目是很简单的。

如果两棵树的节点数不一样的话,这两棵树肯定是不同构的,为此还得定义 m 来控制第二课树的输入。需要注意的是,如果输入第二个节点数后立即判断、输出并 return,而不接收第二棵树的节点,pta 会因为没有完全接收它给出的数据而判错,这是另一道题的前车之鉴了。

没错,我连这种错都范过……

此外,多节点的情况下,根节点保存的字符不同也是显而易见的不同构,这同时解决了第一次进入递归的初始化问题。

c. 实现递归方法的主体内容

一时也想不到其他显而易见的错误了,于是回过头来继续做题。

因为是用递归写的代码,所以要好好规划一下这递归究竟如何使用:

  • 规定:进入递归的两个节点所保存的字符是相同的,即两个节点是同构的,孩子不论。
  • 二、递归判断的是两个节点的孩子是否保存着相同的字符,即判断孩子的同构。
  • 三、return 的是……emmm……一时间想不通这条咋叙述,所以 pass。

其实简单写下的规定还是有不明朗之处,但一时间也想不到许多,于是先把两个节点两个孩子的判断代码搬入递归方法中。方法名是 answer,因为一时间也想不到什么好名字了,甚至连写个 answer 都要百度咋拼

写着写着忽然想到,一个节点不一定有两个孩子,还有可能没有孩子,甚至另一个孩子是根本就不是节点(而是 NULL),一时间觉得写递归的难度飙升,劝退*2。

于是梳理了一下我的递归到底在写什么,记录下三条分支:

  • 每节点一个孩子;
  • 每个节点俩个孩子;
  • 节点无孩子;

废话文学实锤。

两个孩子的情况 pass。

当节点只有一个孩子时,首先要判断究竟是哪个孩子不是孩子(递归码字进行时),即另一个孩子是 NULL。

于是改了之前写的一个 判断两个节点保存的字符是否相同 的方法,来判断某个节点有几个孩子,方便起见,当节点的左孩子为 NULL 时返回 1,右孩子为 NULL 时返回 -1,是参考的横轴左负右正。结果后来还是用错了,纠错时长 + 20min。 方法名 countChildren。

countA = countChildren(pa),pb 同处理。

真正动手判断的时候甚至想过 char tempA = countA>0?pa->right->c:pa->right->c 这种扭曲写法,事实证明代码过于集成反而麻烦,于是改为 P tempA = countA>0?pa->right:pa->right,发现写到 right 的时候编译器没有及时提示,惊觉不靠谱,最终改成了简朴的 if 语句。

其实加上 ( ) () () 就没问题了。

d. 完善递归方法及主方法

当写完只有一个孩子的情况,发现还有个 “节点无孩子” 在等着我,简直累感不爱。

不过代码都已经完成到这一步了,难道还要退缩吗?No.

写只有一个孩子的情况时就想到,如果两个节点的孩子数不同,两节点同样不同构,于是加上了这条判断。

这里还有个小失误,因为 countChildren 返回 1 也返回 -1,得给它套个 abs 才能放心比较,纠错时常 + 20min。

节点无孩子倒是简单,因为规定了进入递归的两节点是同构的,直接返回 1。

至此,递归方法结束。

③ 最终梳理程序

检查一遍主方法后,主方法也结束了。

因为写代码的时候比较细心,六个监测点里只错了一个,还是因为空树返回 Yes 或 No 而错的,说实话有点小开心。这里要是错一大片简直崩溃。

倒是测试用例的时候纠错好长时间,无语无语,冷静冷静,兜住兜住━━( ̄ー ̄*|||━━

5、后记

这不是第一次做出 25 分的题,但是第一次做对树,很有成就感。

至今连根据前序遍历和中序遍历推导树的结构都做不到。

各位看官要是有感而发,欢迎评论区分享。

没有的话就算了,毕竟我也不看这种又臭又长的文(doge

千言万语无墨可落,不必关注,有缘再见。

黑历史 + 1

标签:25,同构,right,return,C语言,pb,pa,节点,left
来源: https://blog.csdn.net/qq_42800984/article/details/121235612

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有