樹的同構 - 樹的基本概念


前言

樹相關的基礎題吧,折騰了挺久,優化的過程挺棒的。

題目

給定兩棵樹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. 用一種合適的方式存儲樹,這里我選擇的是用數字,然后用數組下標作為定位,一個字符數組存儲結點信息,一個二維整型數組存儲左右結點;
  2. 找出兩顆樹各自的根結點為了方便從根結點開始遍歷到底是否同構;
  3. 判斷是否同構,這里用遞歸解決。

實現

完整代碼

#include<iostream>
#include<vector>
using namespace std;

const int MaxSize = 10;

void myInput(char c[], int a[][2], int n);
int myFindRoot(int a[][2], int n);
bool myIsomor(char c1[], char c2[], int child1[][2], int child2[][2], int root1, int root2);

int main() {
	char t1[MaxSize] = { 0, };
	char t2[MaxSize] = { 0, };
	int child1[MaxSize][2];
	int child2[MaxSize][2];
	int num1, num2;
	int root1, root2;
	cin >> num1;
	myInput(t1, child1, num1);
	cin >> num2;
	myInput(t2, child2, num2);

	root1 = myFindRoot(child1, num1);
	root2 = myFindRoot(child2, num2);

	bool res = myIsomor(t1, t2, child1, child2, root1, root2);
	if (res == true)
		cout << "Yes";
	else
		cout << "No";

	return 0;
}

void myInput(char c[], int a[][2], int n) {
	for (int i = 0; i < n; i++) {
		cin >> c[i];
		for (int j = 0; j < 2; j++) {
			char temp;
			cin >> temp;
			if (temp >= '0' &&  temp <= '9')
				a[i][j] = temp - '0';
			else
				a[i][j] = -1;
		}
	}
}

int myFindRoot(int a[][2], int n) {
	int root = -1;			//判定空樹
	int flag[MaxSize] = {0};
	for (int i = 0; i < n; i++) {
		if (a[i][0] >= 0)
			flag[a[i][0]] = 1;
		if (a[i][1] >= 0)
			flag[a[i][1]] = 1;
	}
	for (int i = 0; i < n; i++)
		if (flag[i] == 0)
			root = i;
	return root;
}


bool myIsomor(char c1[], char c2[], int child1[][2], int child2[][2], int root1, int root2) {
	int l1 = child1[root1][0], r1 = child1[root1][1];	
	int l2 = child2[root2][0], r2 = child2[root2][1];
	if (root1 == -1 && root2 == -1)
		return true;
	else if (root1 != -1 && root2 != -1 && c1[root1] == c2[root2])
		return (myIsomor(c1, c2, child1, child2, l1, l2) && myIsomor(c1, c2, child1, child2, r1, r2)) ||
		(myIsomor(c1, c2, child1, child2, l1, r2) && myIsomor(c1, c2, child1, child2, r1, l2));
	else
		return false;
}

存儲

char t1[MaxSize] = { 0, };
char t2[MaxSize] = { 0, };
int child1[MaxSize][2];
int child2[MaxSize][2];

輸入

就是循環,不過有個小細節,就是根據輸入樣例來看,如果沒有子結點的話,樣例輸入的是'-',很明顯這是個字符,與我們用來存儲左右孩子結點的整型數組有沖突,所以在輸入孩子結點前加了一個小小的判定語句,如果沒有子結點,那么選擇用'-1'來標識。

void myInput(char c[], int a[][2], int n) {
	for (int i = 0; i < n; i++) {
		cin >> c[i];
		for (int j = 0; j < 2; j++) {
			char temp;
			cin >> temp;
			if (temp >= '0' &&  temp <= '9')
				a[i][j] = temp - '0';
			else
				a[i][j] = -1;
		}
	}
}

尋找根結點

根結點,就是父結點的點,放在輸入樣例里,就是不會充當其他結點子結點的點,遍歷一下就可以很輕松的找到了。但是!有一個點需要格外的注意!如果輸入的是空樹,那么是無法找到根節點的,所以這時候root的默認值就需要考究一下了,這里設定為-1,也是為接下來判定遞歸方便。

int myFindRoot(int a[][2], int n) {
	int root = -1;			//判定空樹
	int flag[MaxSize] = {0};
	for (int i = 0; i < n; i++) {
		if (a[i][0] >= 0)
			flag[a[i][0]] = 1;
		if (a[i][1] >= 0)
			flag[a[i][1]] = 1;
	}
	for (int i = 0; i < n; i++)
		if (flag[i] == 0)
			root = i;
	return root;
}

判斷同構

彎路版

這里我走了一個小小的彎路,不僅代碼實現過程廢了很多周折,最終答案也出了錯誤。
這是我原本的判斷函數,這個函數默認是傳入的根節點已經判斷過了,然后對子結點進行判斷,再把子結點作為根結點進行判斷。
這里有兩個錯誤,或者說不妥之處:

  1. 如果一開始傳入的兩個根結點是不相同的,判定就會出現錯誤,因為還沒有對根結點進行判斷而又是默認根結點相同的;
  2. 需要列出的情況太多,導致代碼過於復雜,還容易遺漏,因為畢竟四個結點的情況都需要考慮
bool myIsomor_backup(char c1[], char c2[], int child1[][2], int child2[][2], int root1, int root2) {		//這是默認根節點相同,但是不能對第一層無法處理
	int l1 = child1[root1][0], r1 = child1[root1][1];		//可能是-1
	int l2 = child2[root2][0], r2 = child2[root2][1];
	//把需要遞歸解決和可以返回ture的情況列出來,先通過本層葉子結點的檢測再將其作為根節點進行檢查
	if (l1 == -1 && l2 == -1 && r1 == -1 && r2 == -1)		//都沒有子結點
		return true;
	else if (l1 == -1 && l2 == -1 && c1[r1] == c2[r2])
		return myIsomor(c1, c2, child1, child2, r1, r2);
	else if (l1 == -1 && r2 == -1 && c1[r1] == c2[l2])
		return myIsomor(c1, c2, child1, child2, r1, l2);
	else if (r1 == -1 && l2 == -1 && c1[l1] == c2[r2])
		return myIsomor(c1, c2, child1, child2, l1, r2);
	else if (r1 == -1 && r2 == -1 && c1[l1] == c2[l2])
		return myIsomor(c1, c2, child1, child2, l1, l2);
	else if (l1 != -1 && l2 != -1 && r1 != -1 && r2 != -1) {
		if (c1[l1] == c2[l2] && c1[r1] == c2[r2])			//說明這一層通過檢查了,再檢查下一層
			return myIsomor(c1, c2, child1, child2, l1, l2) && myIsomor(c1, c2, child1, child2, r1, r2);
		else if(c1[l1] == c2[r2] && c1[r1] == c2[l2])
			return myIsomor(c1, c2, child1, child2, l1, r2) && myIsomor(c1, c2, child1, child2, r1, l2);
	}
	else
		return false;
}

正確版

這里改正了,換了一種思路,對根結點進行判斷,然后再遞歸判斷子結點。這種方法就可以避免上面的情況。
這里只需要把兩個根結點判定成功的兩種情況列出來再對子結點進行判斷即可:

  1. 都是空的
  2. 非空,相同

從代碼量就可以看出來少了好多...

bool myIsomor(char c1[], char c2[], int child1[][2], int child2[][2], int root1, int root2) {
	int l1 = child1[root1][0], r1 = child1[root1][1];	
	int l2 = child2[root2][0], r2 = child2[root2][1];
	if (root1 == -1 && root2 == -1)            //也是空樹的情況,很神奇的就完成了對空樹的判斷
		return true;
	else if (root1 != -1 && root2 != -1 && c1[root1] == c2[root2])
		return (myIsomor(c1, c2, child1, child2, l1, l2) && myIsomor(c1, c2, child1, child2, r1, r2)) ||
		(myIsomor(c1, c2, child1, child2, l1, r2) && myIsomor(c1, c2, child1, child2, r1, l2));
	else
		return false;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM