树的学习小结
一、知识总结
树这一章和前四章相比概念特别多,有时候做题需要翻老半天书去找定义。为了加深理解,下面我来简单的归纳一下(真希望我有一块手绘板TAT):
二、问题解决
本章实践题第一题:
给定两棵树T1和T2。如果T1可以通过若干次左右孩子互换就变成T2,则我们称两棵树是“同构”的。例如图1给出的两棵树就是同构的,因为我们把其中一棵树的结点A、B、G的左右孩子互换后,就得到另外一棵树。而图2就不是同构的。
图1
图2
输入格式:
输入给出2棵二叉树树的信息。对于每棵树,首先在一行中给出一个非负整数N (≤),即该树的结点数(此时假设结点从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
刚开始拿到题目,我觉得直接找同构树或者分析同构树满足的规律会比较困难,于是我运用逆向思维转化问题:将原树进行左右孩子交换会得到哪些情况?
如一棵结点为7的满二叉树(ABCDEFG),按要求操作会得到2^3-1=7棵同构树。通过观察一般情况,可以得出同构树的数目计算公式为2^m-1,其中m是非叶子结点的结点个数。
寻找这些同构树之间的共同规律较为困难,故想要判断树2是否为树1的同构树,只能采取列举1树同构树的所有情况再将2树与其进行比对的方法,如果完全一样,则说明2是1的同构树。所以有必要再添加一个函数用于判断两棵树是否完全相等。
还有一种可简化的情况值得考虑,如果树2的结点和树1都不完全相同,则根本没有继续进行下去的必要,故可以在操作之前先检验一次。
用到的函数:
BuildTree 建立树并返回其头结点
Isomorphism 判断是否同构
SameTree 判断两棵树是否完全相同
Reverse 交换某一节点的左右子树
实际操作:
以下是写到一半的代码(写不下去……简直没眼看)
#include<iostream> #include<queue> using namespace std; typedef struct{ char name; char lch; char rch; }Node; int BuildTree(Node t[],int N); //接受数据并建立二叉树,返回根节点 bool Isomorphism(Node t[],Node r[]); //判断两棵树是否同构的函数 int main(){ int N,M,m,n; Node t[10]; Node r[10]; cin>>N; BuildTree(t,N); cin>>M; BuildTree(r,M); Isomorphism(t,r); return 0; } bool SameTree(Node t[],Node r[],int x , int y){ // 遍历树,将结点依次入队,比较两条队中每一个吐出的元素是否相同。 queue<char> q; queue<char> p; q.push(x); p.push(y); while(!q.empty() &&!p.empty()){ tmp1 = q.front(); tmp2 = p.front(); if(tmp1!=tmp2){ return false; } q.push(t[tmp1].lch); q.push(t[tmp1].rch); p.push(r[tmp2].lch); p.push(r[tmp2].rch); } return true; } void Reverse(Node t[],int a){ // 交换某一个结点的左右孩子 if(t[a].lch!=false&&t[a].rch!=false){ int tmp = 0; tmp = t[a].lch; t[a].lch = t[a].rch; t[a].rch = tmp; } } int BuildTree(Node t[],int N){ int m,n = 0; char check[10] = {false}; for(int i = 0; i<N ; i++){ cin>>t[i].name>>t[i].lch>>t[i].rch; m = t[i].lch - '0'; if(t[i].lch!='-'){ check[m] = true; } else m = -1; n = t[i].rch - '0'; if(t[i].rch!='-'){ check[n] = true; } else n = -1; } for(int i=0;i<N;i++){ if(!check[i]){ return i; } } }
写到一半的我发现了这种方法的很多问题:一个一个列举交换后的树让人感到让我觉得很头疼,我们先得存储这些树,然后用判断相同算法一个一个进行比对,工程浩大。这时我又转过头来想,到底有没有什么办法可以直接判断是不是同构树呢?即找到同构树要满足的条件?
再看了看同构的概念:如果T1可以通过若干次左右孩子互换就变成T2,则我们称两棵树是“同构”的。从具体的情况讨论,都为空一定同构;一个为空另一个不为空时肯定不同构;都不为空时,该节点数据不同肯定不同构,如果节点数据相同,两个节点的左右孩子“一一对应”或“交叉对应”相等都可以满足同构。这时判断每一个结点是否同构的方法,要判断整棵树是否同构,可以利用递归调用。以下是完整版的代码:
#include <iostream> using namespace std; #define MaxTree 10 #define Null -1 typedef int Tree ; typedef char ElementType ; typedef struct TreeNode TreeNodeArray ; struct TreeNode { ElementType Element ; Tree Left ; Tree Right ; }; struct TreeNode Tree1[MaxTree] , Tree2[MaxTree] ; Tree BuildeTree( TreeNodeArray Tree[] ) { // 建立树并返回根节点 int i,N,check[MaxTree]; char lch,rch; int Root; cin>>N; if( N!=0 ) { for( i=0 ; i<N ; i++ ) check[i] = 0 ; for( i=0 ; i<N ; i++ ) { //结点是孩子结点就将它标记为1 cin>>Tree[i].Element >>lch>>rch ; if( lch != '-' )//如果结点有左孩子结点,将左孩子结点标记 { Tree[i].Left = lch-'0' ; check[ Tree[i].Left ] = 1 ; } else Tree[i].Left = Null ; if( rch!= '-' )//如果结点有右孩子结点,将右孩子结点标记 { Tree[i].Right = rch-'0' ; check[ Tree[i].Right ] =1 ; } else Tree[i].Right = Null ; } } else return Null ; for(i=0 ; i<N ; i++ ) { if(check[ i ]==0 ) break ; } Root = i ; return Root ; } int Isomorphic( Tree Root1,Tree Root2 ) { //如果为空树则是同构的 if( (Root1== Null) && (Root2== Null) ) return 1 ; //如果一个为空一个不为空则不是同构的 else if( (Root1== Null) || (Root2 == Null) ) return 0 ; //如果数据不同则不是同构的 else if( Tree1[Root1].Element != Tree2[Root2].Element) return 0 ; //递归调用 else if ( Isomorphic(Tree1[Root1].Left,Tree2[Root2].Left) && Isomorphic( Tree1[Root1].Right,Tree2[Root2].Right ) ) return 1; else if ( Isomorphic(Tree1[Root1].Left,Tree2[Root2].Right) && Isomorphic( Tree1[Root1].Right,Tree2[Root2].Left ) ) return 1; return 0; } int main( ) { int i ; Tree Root1 , Root2 ; Root1 = BuildeTree( Tree1 ) ; Root2 = BuildeTree( Tree2 ) ; if( Isomorphic( Root1,Root2 ) ) cout<<"Yes"; else cout<<"No"; return 0 ; }
我在看了班上另外一个同学的博客之后发现,居然有人和我的第一种想法类似并且实现了!但是他的方法比我的更聪明,没有把每一棵树列出来然后进行比较,而是“化整为零”,把研究的层面降到了结点,即如果树1和树2同构,则一定有树1的任意一个节点可以在树2中找到,这个方法很巧妙。