树的学习小结

一、知识总结

树这一章和前四章相比概念特别多,有时候做题需要翻老半天书去找定义。为了加深理解,下面我来简单的归纳一下(真希望我有一块手绘板TAT):

 

 

 

 

 

二、问题解决

本章实践题第一题:

 

7-1 树的同构 (30 分)
 

 

给定两棵树T1和T2。如果T1可以通过若干次左右孩子互换就变成T2,则我们称两棵树是“同构”的。例如图1给出的两棵树就是同构的,因为我们把其中一棵树的结点A、B、G的左右孩子互换后,就得到另外一棵树。而图2就不是同构的。

 


 

图1

图2

现给定两棵树,请你判断它们是否是同构的。

 

输入格式:

输入给出2棵二叉树树的信息。对于每棵树,首先在一行中给出一个非负整数N (≤),即该树的结点数(此时假设结点从0到N1编号);随后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中找到,这个方法很巧妙。