浙大数据结构第三周之03-树1 树的同构

题目详情:

给定两棵树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

主要思路:

先要理解这句话:对于每棵树,首先在一行中给出一个非负整数N (≤10),即该树的结点数(此时假设结点从0到N−1编号);随后N行,第i行对应编号第i个结点

这句话的意思是:第i行节点编号是i,但建树时可不一定按0~n-1从左到右从上到下来建,而要根据节点关系来

难点主要分为两大部分:

(一)建树:

用一个大数组来建树,大数组的下标i对应节点i,在过程中找到根节点,扫描树时利用根节点先序遍历

(1)先初始化大数组,将每个节点存储的character设置为EMPTY,状态设置为true

(2)再遍历输入,其中处理后面子节点有讲究,应该先按字符读取,然后再判断,如果是数字,就改为数字后存入节点,否则说明子节点为空,并将子节点状态定义为false

(3)遍历大数组,其中节点character不为EMPTY且状态不为false的就是根节点

(二)判断是不是同构,用到前序遍历、递归三部曲:

(1)参数和返回值

参数:两棵树各自根节点;返回值:以当前根节点是否满足题意

(2)结束条件:

遍历到空节点

(3)遍历顺序:

前序遍历,其中的单层递归逻辑:先判断当前两个根节点是否相等,再看各自左孩子与各自右孩子是否相等,如果相等直接分别递归,如果不相等,就将第一棵子树左孩子传入下一层递归与第二棵子树右孩子比较;第一棵子树右孩子传入下一层递归与第二棵子树左孩子比较

第一次写错误:

(1)这道题有一个非常细节的点,就是如何定义表示空节点的常量NONE,当读入的符号是‘-’时,说明是空节点,如果将NONE定义为-1,那么当-1是不能用数组表示的,因为数组下标从0开始,所以因为本题数据不大(小于10),所以将最大数据MAX_SIZE定义为15,NONE定义为14(还不理解可以看代码里注释)

(2)树可能是空数,判断时要额外加一条

代码实现:

#include <stdio.h>
#include <stdbool.h>
#define MAX_SIZE 15
#define EMPTY '-'
#define NONE 14
/*定义树的数据结构*/
typedef struct TreeNode Tree;
struct TreeNode {
    char Character;
    int LeftChild;
    int RightChild;
    bool IsRoot;
};
/*建树*/
int BuildTree(int nodeNum, Tree* tree) {
    for(int i = 0; i < nodeNum; i++) {  //首先初始化树的结构体数组
        tree[i].Character = EMPTY;
        tree[i].IsRoot = true;
    }
    for(int i = 0; i < nodeNum; i++) {  //读入树的结构体数组
        char charLeftChild, charRightChild;
        scanf("%c %c %c", &(tree[i].Character), &charLeftChild, &charRightChild);
        getchar();  //读取换行符
        if(charLeftChild >= '0' && charLeftChild <= '9') {
            tree[i].LeftChild = charLeftChild - '0';
            tree[tree[i].LeftChild].IsRoot = false;
        }
        else if(charLeftChild == EMPTY) {
            tree[i].LeftChild = NONE;    //建树的这里很关键,因为树如果将NONE定义为-1,则下面tree[i].LeftChild就是-1,数组是没有-1的下标的
            tree[tree[i].LeftChild].Character = EMPTY;    //一定要有这步,因为这样在下面判断递归时才可以判断空节点(理解不了就debug)
        }
        if(charRightChild >= '0' && charRightChild <= '9') {
            tree[i].RightChild = charRightChild - '0';
            tree[tree[i].RightChild].IsRoot = false;
        }
        else if(charRightChild == EMPTY) {
            tree[i].RightChild = NONE;
            tree[tree[i].RightChild].Character = EMPTY;
        }
    }
    int rootId = -1;
    for(int i = 0; i < nodeNum; i++) {
        if(tree[i].Character != EMPTY && tree[i].IsRoot == true) {
            rootId = i;
            break;
        }
    }
    return rootId;
}
/*通过前序遍历判断是否同构*/
bool Judge(Tree* tree1, int root1, Tree* tree2, int root2) {
    //终止条件:遍历到空节点
            if(root1 == NONE && root2 == NONE) {  //两个都为空,返回true
        return true;
    }
    if((root1 == NONE && root2 != NONE) || (root1 != NONE && root2 == NONE)) {  //两个都不为空,返回false
        return false;
    }

    //前序遍历
    if(tree1[root1].Character == tree2[root2].Character) {  //如果根节点相同
        bool leftSubTree;
        bool rightSubTree;
        if(tree1[tree1[root1].LeftChild].Character == tree2[tree2[root2].LeftChild].Character &&  //左右子树对应相同
           tree1[tree1[root1].RightChild].Character == tree2[tree2[root2].RightChild].Character) {
                leftSubTree = Judge(tree1, tree1[root1].LeftChild, tree2, tree2[root2].LeftChild);
                rightSubTree = Judge(tree1, tree1[root1].RightChild, tree2, tree2[root2].RightChild);
                if(leftSubTree && rightSubTree) {
                    return true;
                }
                else return false;
        }    
        else {  //如果左右子树对应不同,就看1的左子树与2的右子树是否相同
            leftSubTree = Judge(tree1, tree1[root1].RightChild,
                                tree2, tree2[root2].LeftChild);
            rightSubTree = Judge(tree1, tree1[root1].LeftChild,
                                 tree2, tree2[root2].RightChild);
            if(leftSubTree && rightSubTree) return true;
            else return false;
        }
    }
    else return false;  //如果根节点不同不用看了,直接false
}

int main() {
    //建第一棵树
    int treeNode1;
    scanf("%d", &treeNode1);
    getchar();
    Tree tree1[MAX_SIZE];
    int root1 = BuildTree(treeNode1, tree1);

    //建第二棵树
    int treeNode2;
    scanf("%d", &treeNode2);
    getchar();
    Tree tree2[MAX_SIZE];
    int root2 = BuildTree(treeNode2, tree2);

    if(Judge(tree1, root1, tree2, root2) && treeNode1 >= 1 && treeNode2 >= 1) {
        printf("Yes");
    }
    else if(treeNode1 == 0 && treeNode2 == 0) {
        printf("Yes");
    }
    else printf("No");
    
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值