1 题目原文
题目链接:7-3 树的同构
给定两棵树 T 1 T_1 T1 和 T 2 T_2 T2。如果 T 1 T_1 T1 可以通过若干次左右孩子互换就变成 T 2 T_2 T2,则我们称两棵树是“同构”的。例如图 1 1 1 给出的两棵树就是同构的,因为我们把其中一棵树的结点 A 、 B 、 G A、B、G A、B、G 的左右孩子互换后,就得到另外一棵树。而图 2 2 2 就不是同构的。
图1
图2
现给定两棵树,请你判断它们是否是同构的。
输入格式:
输入给出 2 2 2 棵二叉树的信息。对于每棵树,首先在一行中给出一个非负整数 n ( ≤ 10 ) n (≤10) n(≤10),即该树的结点数(此时假设结点从 0 0 0 到 n − 1 n−1 n−1 编号);随后 n n n 行,第 i i i 行对应编号第 i i i 个结点,给出该结点中存储的 1 1 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
2 思路解析
本题考查二叉树的相关知识。由题意可以了解到,二叉树的每个结点不是必须交换左右子树。判断二叉树是否同构在本质上是判断两棵二叉树是不是“相同”的,只是这个“相同”不是左子树和左子树“相同”,右子树和右子树“相同”,而是左子树可能和左子树“相同”,也可能和右子树“相同”,右子树同理。
将上面的“相同”替换成“同构”,于是递归思路便呼之欲出,具体如下:
1. 判断二叉树 A
和二叉树 B
是否同构;
2. 若 A
是空树且 B
是空树,则同构;
3. 若一棵树是空树但另一棵树不是空树,则不同构;
4. 若两棵树都不是空树,则同构需满足:
4.1 当前结点的值相同;
4.2 当前 A
结点的左子树和 B
的右子树相同且当前 A
结点的右子树和 B
的左子树相同或当前 A
结点的左子树和 B
的左子树相同且当前 A
结点的右子树和 B
的右子树相同。
递归体现在 4.2
中。
这道题的迭代思路和递归思路类似,甚至有点相当于将递归硬改成迭代了,意义不大,故不做记录。
3 代码实现
需要注意的是,此题中给出的树是采用的“孩子表示法”表示的,但存储方式是顺序存储的,与一般的“二叉链表表示法”(链式存储)有所不同,这里将其“统一化”为二叉链表表示法,然后进行处理。
#include <stdio.h>
#include <stdlib.h>
// 二叉树结点定义
typedef struct TNode {
char val;
struct TNode *lc, *rc;
} TNode, Tree;
// 根据孩子表示法的数组构建二叉链表树
Tree *create_tree(char tree_arr[][3], int n, char null) {
TNode *tn_arr[n];
int i = 0, p[n];
// 第一遍先创建所有结点并初始化“是否有父结点”的标志数组
for (i = 0; i < n; i++) {
tn_arr[i] = (TNode *)malloc(sizeof(TNode));
tn_arr[i]->val = tree_arr[i][0];
tn_arr[i]->lc = tn_arr[i]->rc = NULL;
p[i] = 0;
}
// 第二遍将这些结点组合成树
for (i = 0; i < n; i++) {
if (tree_arr[i][1] != null) {
tn_arr[i]->lc = tn_arr[tree_arr[i][1] - '0'];
p[tree_arr[i][1] - '0'] = 1;
}
if (tree_arr[i][2] != null) {
tn_arr[i]->rc = tn_arr[tree_arr[i][2] - '0'];
p[tree_arr[i][2] - '0'] = 1;
}
}
// 第三遍找出根结点并返回这棵树
for (i = 0; i < n; i++) {
if (!p[i]) return tn_arr[i];
}
return NULL;
}
// 判断两棵链式二叉树是否同构
int is_isomo(Tree *r1, Tree *r2) {
// 两个都是空树,同构
if (!r1 && !r2) return 1;
// 一个是空树一个不是,不同构
if (!r1 || !r2) return 0;
// 两个都不是空树
return r1->val == r2->val &&
(is_isomo(r1->lc, r2->lc) && is_isomo(r1->rc, r2->rc) ||
is_isomo(r1->lc, r2->rc) && is_isomo(r1->rc, r2->lc));
}
// 销毁二叉树
void destroy_tree(Tree *root) {
if (!root) return;
destroy_tree(root->lc);
destroy_tree(root->rc);
free(root);
}
int main(void) {
// 读入数据
int n = 0, m = 0, i = 0;
scanf("%d", &n);
char tree_a[n][3];
for (i = 0; i < n; i++) {
scanf(" %c %c %c", &tree_a[i][0], &tree_a[i][1], &tree_a[i][2]);
}
scanf("%d", &m);
if (n ^ m) {
printf("No\n");
return 0;
}
char tree_b[m][3];
for (i = 0; i < m; i++) {
scanf(" %c %c %c", &tree_b[i][0], &tree_b[i][1], &tree_b[i][2]);
}
// 建树
Tree *aa = create_tree(tree_a, n, '-');
Tree *bb = create_tree(tree_b, m, '-');
// 判断是否同构
printf("%s\n", is_isomo(aa, bb) ? "Yes" : "No");
// 销毁
destroy_tree(aa);
destroy_tree(bb);
return 0;
}
4 总结
判断二叉树的同构,其解决方法与判断二叉树是否相同类似,并且天然地可以使用递归的解法解决。关于树与二叉树的问题,如果可以用迭代解决的优先选择迭代,不过有时候迭代的思路并不好想,或者迭代的思路很复杂,那么使用递归解决就好。