死磕算法·字符串问题】判断A中是否存在一棵子树与B树的拓扑结构完全相同·kmp算法应用

题目大意:

对于两棵彼此独立的二叉树A和B,请编写一个高效算法,检查A中是否存在一棵子树与B树的拓扑结构完全相同。给定两棵二叉树的头结点A和B,请返回一个bool值,代表A中是否存在一棵同构于B的子树。

许多题目可以转化为字符串类型题目进行求解。此题判断A中是否有一棵拓扑结构和B相同的子树,可以遍历两棵树为两个字符串(这里遍历和普通前序/中序/后序遍历不同),再用KMP算法进行判断即可。

kmp算法可用来判断某字符串B是不是字符串A的子串,详见如何更好的理解和掌握 KMP 算法?

首先明确子树的定义:只要包含了一个结点,就得包含这个结点下的所有节点.

刚刚提到,这道题中遍历出来的字符串不能仅仅存储数字,还要存储这个节点是父节点还是子节点。


《死磕算法·字符串问题】判断A中是否存在一棵子树与B树的拓扑结构完全相同·kmp算法应用》
《死磕算法·字符串问题】判断A中是否存在一棵子树与B树的拓扑结构完全相同·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中是否存在一棵子树与B树的拓扑结构完全相同·kmp算法应用》

《死磕算法·字符串问题】判断A中是否存在一棵子树与B树的拓扑结构完全相同·kmp算法应用》
中序遍历结果为:
A:”#!4!#!2!#!5!#!1!#!6!#!3!#!7!#!”
B:”#!1!#!
红色部分是可以匹配上的,但明显后者不是前面一棵树的子树。

总结

1、子树判断问题转化为子字符串匹配问题,用kmp算法搞定。
2、需要依靠字符辅助记录某结点是否是子节点,不能简单遍历记录树的元素。
3、遍历可以采用前序遍历、后序遍历,中序遍历不符合本题情况。

    原文作者:KMP算法
    原文地址: https://blog.csdn.net/gulaixiangjuejue/article/details/85007294
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞