给定两棵树T1和T2。如果T1可以通过若干次左右孩子互换就变成T2,则我们称两棵树是“同构”的。例如图1给出的两棵树就是同构的,因为我们把其中一棵树的结点A、B、G的左右孩子互换后,就得到另外一棵树。而图2就不是同构的。
图1
图2
现给定两棵树,请你判断它们是否是同构的。
输入格式:
输入给出2棵二叉树树的信息。对于每棵树,首先在一行中给出一个非负整数N (≤10),即该树的结点数(此时假设结点从0到N−1编号);随后N行,第i行对应编号第i个结点,给出该结点中存储的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
解法一:
此题可以使用树的孩子表示法,注意这里可以不使用链表,而直接可以通过数组的下标来确定父亲与孩子之间的关系。
建立结构体:
struct Node{
char alpha;
int lchild;
int rchild;
}tree1[15],tree2[15];
其中alpha表示结点的值,lchild表示左孩子,rchild表示右孩子。
第一种方法我是通过遍历第一个树,然后从第二个树中找到和第一个树相同的结点,再比较它们的子结点是否相同,如果不同则直接打印No并退出,如果相同则继续遍历。这里有一个特殊情况就是当树的结构相同但数值不同时,返回的是“No”。
代码如下:
#include<iostream>
#include<stdio.h>
using namespace std;
struct Node{
char alpha;
int lchild;
int rchild;
}tree1[15],tree2[15];
int main()
{
int n1,n2;//结点数
cin >> n1;
char a,b,c;
for(int i = 0;i < n1;i++)
{
cin >> a >> b >> c;
tree1[i].alpha = a;
if(b!='-') tree1[i].lchild = b - '0';
else tree1[i].lchild = -1;
if(c!='-') tree1[i].rchild = c - '0';
else tree1[i].rchild = -1;
}
cin >> n2;
for(int i = 0;i < n2;i++)
{
cin >> a >> b >> c;
tree2[i].alpha = a;
if(b!='-') tree2[i].lchild = b - '0';
else tree2[i].lchild = -1;
if(c!='-') tree2[i].rchild = c - '0';
else tree2[i].rchild = -1;
}
if(n1 != n2)
{
cout << "No\n";
return 0;
}
char temp1,temp2,temp3,temp4;
bool find_out = false;
for(int i = 0;i < n1;i++)
{
if(tree1[i].lchild != -1)
{
temp1 = tree1[tree1[i].lchild].alpha;
}
else
{
temp1 = '-';
}
if(tree1[i].rchild != -1)
{
temp2 = tree1[tree1[i].rchild].alpha;
}
else
{
temp2 = '-';
}
for(int j = 0;j < n2;j++)
{
if(tree2[j].alpha == tree1[i].alpha)
{
find_out = true;
if(tree2[j].lchild!=-1)
{
temp3 = tree2[tree2[j].lchild].alpha;
}
else
{
temp3 = '-';
}
if(tree2[j].rchild!=-1)
{
temp4 = tree2[tree2[j].rchild].alpha;
}
else
{
temp4 = '-';
}
if(!((temp1==temp3&&temp2==temp4)||(temp1==temp4&&temp2==temp3)))
{
cout << "No" << endl;
return 0;
}
}
}
if(!find_out)
{
cout << "No" << endl;
return 0;
}
}
cout << "Yes" << endl;
return 0;
}
解法二:
这种方法就比较简单,先和第一种方法一样使用一个结构体数组来表示树,然后在建树的时候找到树的根结点并保存下来(寻找根结点的方法就是寻找在树的左孩子和右孩子中没有出现的数);找到根结点后可以通过从根结点往下逐次比较。这里有三种情况:
- 判断根结点是否为空,若两个根结点都为空则是同构树;如果一个为空一个不为空则不是同构树
- 两个根结点都存在,但是对应的数值不同,不是同构树
- 判断两个根结点的左右子树是否相同或者是否是顺序调换。
注意上面三种情况是通过递归来判断的。
#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;
#define NULL -1
struct Node
{
char alpha;
int lchild;
int rchild;
}tree1[15], tree2[15];
//建树并返回树的根结点
int BuildTree(struct Node tree[])
{
int n;
int label[15];
char data, left, right;
memset(label, 0, sizeof(label));//初始化
cin >> n;
for (int i = 0; i < n; i++)
{
cin >> data >> left >> right;
tree[i].alpha = data;
if (left != '-')
{
tree[i].lchild = left - '0';
label[left - '0'] = 1;
}
else
tree[i].lchild = NULL;
if (right != '-')
{
tree[i].rchild = right - '0';
label[right - '0'] = 1;
}
else
tree[i].rchild = NULL;
}
int root = NULL;//初始化根结点
for (int i = 0; i < n; i++)
{
if (!label[i]) root = i;
}
return root;
}
bool judge(int root1, int root2)
{
if (root1 == NULL && root2 == NULL) {//判断根节点是否都为空
return true;//都为空则相同
}
if (root1 == NULL && root2 != NULL || root1 != NULL && root2 == NULL) {
return false;//一个根节点为空另外一个不为空则为假
}
if (tree1[root1].alpha != tree2[root2].alpha) {
return false;//两棵树根节点都存在,但数值不同则为假
}
if (tree1[tree1[root1].lchild].alpha == tree2[tree2[root2].lchild].alpha) {
//如果左子树的值相等则判断右子树
return judge(tree1[root1].rchild, tree2[root2].rchild);
}
else {
//否则判断是否第二棵树是在第一课树左右子树调换之后得到的
return judge(tree1[root1].lchild, tree2[root2].rchild)
&& judge(tree1[root1].rchild, tree2[root2].lchild);
}
}
int main()
{
int root1 = BuildTree(tree1);
int root2 = BuildTree(tree2);
if (judge(root1, root2))
cout << "Yes\n";
else
cout << "No\n";
return 0;
}