欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

KMP算法

程序员文章站 2022-05-02 17:37:58
...

如果要解决问题:有两个字符串 str1,str2,求 str1 是否包含 str2 ,如果包含,求位置

------ 一般方法 -------
每次从 str2 的 0 位置开始,依次和 str1 匹配
例如 aaab 匹配 ab
先 从 ab 的 a 开始匹配,可以匹配到 aaab 的 a,但是匹配不到 aaab 的 b
从 ab 的 a 开始匹配,匹配 aaab 的第二个 a 开始匹配,这次匹配还不行
第三次 可以匹配到
如果 str1 的长度为 N,str2 的长度为 M,则时间复杂度为 O(N*M)


KMP 算法:让前面匹配过的信息指导后面的匹配
例如字符串 abcabcd
先拿 一个字符,从前往后拿是 a,从后往前拿是 c,a≠c,不匹配
拿 两个字符,从前往后是 ab,从后往前是 bc,ab≠bc,不匹配
拿 三个字符,从前往后是 abc,从后往前是 abc,abc=abc,匹配,d=3
拿 四个字符,得到 abca 和 cabc,不匹配
拿 五个字符,得到 abcab 和 bcabc,不匹配
不能继续再拿,因为拿的字符数量不能超过原本数量,因此 最长匹配度 为 d=3,d 暂存数据 d=3 ,也即 d 的最长前缀和最长后缀匹配度为 d=3
例如字符串 aaaaab
一直拿,可拿 4个字符还是匹配的,所以 暂存数据 b=4,也就是说 b的最长前缀和最长后缀匹配度为 4

KMP 算法
先求 str2 的最长前缀和最长后缀匹配度的信息
例如字符串 ababac
先建立int[]
int[0] = -1 (开始的字符规定是-1)
int[1] = 0 (第二个字符规定是0)
int[2] = 第一个和第二个字符相等吗?1:0
int[……] = 按照方法快速填充数组
最终的 int[] = {-1 0 0 1 2 3}

假设

假设, str2 从 0 位置开始,一直可以匹配 str1(从i位置开始匹配),但是,突然在 Y 位置 和 X 位置不匹配了
我们又知道, str2 字符串的 最长前缀和最长后缀的匹配信息,如上图的两个椭圆

流程
将 str2 的头,和 j 位置对其,再从 X 和 Z 位置开始匹配即可
可以看到,X Y Z 前面的那一串字符串片段是一样的,因此,无需让 str2 和 str1 的那段再次匹配

KMP 认为,从 i 位置 到 j 位置,一律不能配出 str2,这就加速了匹配过程
如果能匹配处 str2 会与 str2 的最长前缀和最长后缀发生矛盾

例子: str1 = abkababkabD str2 = abkababkabF

next[] 的求法
next[0] next[1] next[2] 可以直接赋值
next[i] 的求法 利用归纳法
如果 next[i-1] 已知,且 next[i-1] = a, 那么只需要看 str2[a] 是否等于 str2[i] 即可
如果相等,那么 next[i] = a+1
如果不等,那么 有下面的步骤

package kmp;

public class KMP {

public static int getIndexOf(String s, String m) {
	
	if(s == null || m == null || m.length()<1 || s.length()<m.length()) {
		return -1;
	}
	
	char[] str1 = s.toCharArray();
	char[] str2 = m.toCharArray();
	int[] next = getNextArray(str2);
	int i1 = 0;
	int i2 = 0;
	
	while(i1<str1.length && i2<str2.length) {
		if(str1[i1] == str2[i2]) {
			i1++;
			i2++;
		} else {
			//如果当前字符不匹配,且 str2 已经来到了 第 0 个字符的
			//i1指针 ++
			if(next[i2] == -1) {
				i1++;
			} else {
				//当前字符不匹配,且 str2 没有来到 第 0 个字符
				//i2 往右推进
				i2 = next[i2];
			}
			
		}
	}
	//如果 str2 已经划过了所有字符  那么肯定包含此字符串
	return i2 == str2.length? i1-i2:-1;
}

private static int[] getNextArray(char[] str2) {
	if(str2 == null || str2.length<1) return null;
	if(str2.length == 1) return new int[]{-1};
	int[] next = new int[str2.length];
	next[0] = -1;
	next[1] = 0;
	int i = 2;
	int cn = 0;
	while(i<next.length) {
		if(str2[i-1] == str2[cn]) {
			//匹配的话  看 cn 位置的字符是否等于待比较的字符
			next[i] = cn + 1;
			i = i + 1;
			cn = cn + 1;
		} else if(cn > 0){
			//不匹配 往前跳
			cn = next[cn];
		} else {
			//无法继续跳   那么最长前缀和最长后缀匹配 长度为 0
			next[i] = 0;
			i = i + 1; 
		}
	}
	return next;
}

}

例题1:判断一棵树是否为另一棵树的子树
将树序列化,如果是存在的节点,用 1 标记,如果是空节点,用 # 占位符标记

可以以先序遍历的方式序列化为 1_1_1_##1##1_1###_
可以用 序列化字符串 反序列化重新建树

将两棵树序列化,如果 str2 是 str1 的字串,那么 树2 必是 树1 的子树
因为 是有 # 占住位了

相关标签: KMP 算法