1. 程式人生 > >死磕演算法·字串問題】判斷A中是否存在一棵子樹與B樹的拓撲結構完全相同·kmp演算法應用

死磕演算法·字串問題】判斷A中是否存在一棵子樹與B樹的拓撲結構完全相同·kmp演算法應用

題目大意:

對於兩棵彼此獨立的二叉樹A和B,請編寫一個高效演算法,檢查A中是否存在一棵子樹與B樹的拓撲結構完全相同。給定兩棵二叉樹的頭結點A和B,請返回一個bool值,代表A中是否存在一棵同構於B的子樹。

許多題目可以轉化為字串型別題目進行求解。此題判斷A中是否有一棵拓撲結構和B相同的子樹,可以遍歷兩棵樹為兩個字串(這裡遍歷和普通前序/中序/後序遍歷不同),再用KMP演算法進行判斷即可。

kmp演算法可用來判斷某字串B是不是字串A的子串,詳見如何更好的理解和掌握 KMP 演算法?

首先明確子樹的定義:只要包含了一個結點,就得包含這個結點下的所有節點.

剛剛提到,這道題中遍歷出來的字串不能僅僅儲存數字,還要儲存這個節點是父節點還是子節點。


在這裡插入圖片描述
在這裡插入圖片描述
如果只看非空節點遍歷結果,{1}包含在{1,2,4,5,3,6,7}中,後面一棵樹應當是前面一棵樹的子樹才對。 但是第二棵樹中1的兩個子節點都為null,和前面樹中1的兩個節點不一致,不符合子樹的定義。

因此我們需要在字串中加入能夠表示節點是父節點還是子節點的其他資訊。在遍歷樹生成的字串中,用‘!’表示一個節點遍歷結束,用’#'表示該節點為null。

那麼在上一個例子中,兩棵樹前序遍歷的結果就是
A:“1!2!4!#!#!5!#!#!3!6!#!#!7!#!#!”
B:“1!#!#!”
這樣再用kmp演算法進行匹配,就可以明顯得出B不是A 的子樹了。

class IdenticalTree {
public:
	char a[200]; char b[200]; int next[200]; int index = 0;
	int indexB = 0;
	void getStringA(TreeNode* A) {
		   if (A == NULL) {
			a[index++] = '#';
            a[index++] = '!';
           }
           else{
           a[index++]=A->val;
           a[index++]='!';
		   getStringA(A->left);
		   getStringA(A->right);
           }
	}
	void getStringB(TreeNode* B) {
		   if (B == NULL) {
			b[indexB++] = '#';
		      b[indexB++] = '!';
           }
           else{
           b[indexB++]=B->val;
           b[indexB++]='!';
		   getStringB(B->left);
		   getStringB(B->right);
           }
	}
	//獲得next陣列
	void getnext(char pattern[], int n) {
		int i = 0, j = -1;
		next[0] = -1;
		while (i<n) {
			if (j == -1 || pattern[i] == pattern[j]) {
				i++;
				j++;
				next[i] = j;
			}
			else
				j = next[j];
		}
	}
	int kmp(char a[], int alen, char pattern[], int plen) {
		int i = 0; int j = 0;
		while (i<alen && j <plen) {
			if (j == -1 || a[i] == pattern[j])
			{
				i++;
				j++;

			}
			else {
				j = next[j];
			}
		}
		if (j == plen) {
			return (i - j);
		}
		else
			return -1;
	}
	bool chkIdentical(TreeNode* A, TreeNode* B) {
		// write code here
		getStringA(A);
		getStringB(B);
		getnext(b, indexB);
		if (kmp(a, index, b, indexB) != -1)
			return true;
		else
			return false;
	}
};

遍歷時改為後序遍歷也是正確的,遍歷樹的時候將遍歷順序改變即可。

//後序遍歷
void getStringA(TreeNode* A) {
		if (A != NULL) {
			getStringA(A->left);
            getStringA(A->right);
			a[index++] = A->val;
			a[index++] = '!';
		
		}
		else {
			a[index++] = '#';
			a[index++] = '!';
		}
	}
	void getStringB(TreeNode* B) {
		if (B != NULL) {
			getStringB(B->left);
            getStringB(B->right);
			b[indexB++] = B->val;
			b[indexB++] = '!';	}
		else {
			b[indexB++] = '#';
			b[indexB++] = '!';
		}

	}

在這裡注意,中序遍歷樹是不能判斷子樹問題的,如
在這裡插入圖片描述

在這裡插入圖片描述
中序遍歷結果為:
A:"#!4!#!2!#!5!#!1!#!6!#!3!#!7!#!"
B:"#!1!#!"
紅色部分是可以匹配上的,但明顯後者不是前面一棵樹的子樹。

總結

1、子樹判斷問題轉化為子字串匹配問題,用kmp演算法搞定。
2、需要依靠字元輔助記錄某結點是否是子節點,不能簡單遍歷記錄樹的元素。
3、遍歷可以採用前序遍歷、後序遍歷,中序遍歷不符合本題情況。