1. 程式人生 > >PAT 7-3 樹的同構 (25 分)

PAT 7-3 樹的同構 (25 分)

給定兩棵樹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

分析:(有點思路的同學可以直接看第四點)
1.新建結構體struct TreeNode,以陣列形式(靜態連結串列)存放每棵樹。

typedef struct TreeNode Tree;
struct TreeNode{
    char Data;
    int Left, Right;
};

2.按照題意,main函式大概可以寫成下面這個樣子:第一部分定義變數,第二部分輸入,第三部分比較並輸出

int main()
{
    int n, m;
    Tree T1[10], T2[10];
    cin>>n;
    char a,b,c;

    for(int i=0; i<n; i++){
        cin>>a>>b>>c;
        T1[i].Data=a-'0';
        if(b=='-') T1[i].Left=45;
        else T1[i].Left=b-'0';
        if(c=='-') T1[i].Right=45;
        else T1[i].Right=c-'0';
    }
    cin>>m;
    for(int i=0; i<m; i++){
        cin>>a>>b>>c;
        T2[i].Data=a-'0';
        if(b=='-') T2[i].Left=45;
        else T2[i].Left=b-'0';
        if(c=='-') T2[i].Right=45;
        else T2[i].Right=c-'0';
    }

    if(Compare(T1, T2, n, m)) cout<<"Yes";
    else cout<<"No";

    return 0;
}

3.用Compare函式找到每個樹的根並進行比較:

bool Compare(Tree* A, Tree* B, int N, int M){
    if(N!=M) return false;
    int Root1=0, Root2=0;
    int check[10][2] = {0};
    //找根節點
    for(int i=0; i<N; i++){
        if(A[i].Left!=45) check[A[i].Left][0]=1;
        if(A[i].Right!=45) check[A[i].Right][0]=1;
        if(B[i].Left!=45) check[B[i].Left][1]=1;
        if(B[i].Right!=45) check[B[i].Right][1]=1;
    }
    for(int i=0; i<N; i++){
        if(check[i][0]==0 && !Root1)
            Root1=i;//A樹根節點
        if(check[i][1]==0 && !Root2)
            Root2=i;//B樹根節點
    }
    
    return comp(A, B, Root1, Root2);
}

4.根據輸入的兩個“樹列”和他們的根節點位置,遞迴判斷是不是同構。這個時候就要進行分類討論:

bool comp(Tree* A, Tree *B, int R1, int R2){
    if(R1==45 && R2==45) return true;//遞迴結束1
    if((R1==45&&R2!=45) || (R1!=45&&R2==45)) return false;//遞迴結束2
    if(A[R1].Data!=B[R2].Data) return false;//根節點不同,遞迴結束
    
    if(A[R1].Left==45 && B[R2].Left==45) return comp(A, B, A[R1].Right, B[R2].Right);//均沒有左子樹
    if((A[R1].Left!=45 && B[R2].Left!=45) &&
       (A[A[R1].Left].Data == B[B[R2].Left].Data))//有左子樹且左子樹相同
        return comp(A,B,A[R1].Left,B[R2].Left) &&
               comp(A,B,A[R1].Right,B[R2].Right);
    else//1.有左子樹但不同,左右右左;2.有一個沒有左子樹,左右右左
        return comp(A,B,A[R1].Left,B[R2].Right) &&
               comp(A,B,A[R1].Right,B[R2].Left);
}

最重要的是最後三種情況:

1.均沒有左子樹;
2.均有左子樹,且左子樹相同
3.(此時A,B至少有一個樹有左子樹)分兩種情況:
------>3.1都左子樹但不同。
------>3.2有一個沒有左子樹。
仔細考慮發現,分類討論思想已經構成完備集;遞迴結束有基本情況。因此可以使用這種方法。

這道題裡用45代表“-”是因為本來打算用scanf("%d",&a)的形式直接把‘-’轉換為ASCII碼(45),然後直接用45就好了。後來發現轉換沒有成功。