子字符串查找之确定有限状态自动机 KMP

暴力子字符串查找
显示回退写法

int i, j;
for(i=0, j=0; i<N && j<M; i++){
	if(txt.charAt(j) == pat.charAt(i))  j++;
	else						{i -= j; j = 0;}
}
if(j == M) return i-j;
else 		return N;
  • 暴力子字符串查找时间复杂度为O(~NM),一般在O(N)左右。

确定有限状态自动机
1 基本思想是当出现不匹配时,就能知晓一部分文本的内容,它们就是模式字符串的前 j-1 个字符;
2 原理就是不用回退文本指针,只要回退模式指针,这可以通过确定有限状态自动机或前缀函数实现,既dfa数组和next数组;dfa数组是指在比较了(i)和(j)之后应该和(i+1)比较的模式字符位置;
(关于KMP next数组可见:https://blog.csdn.net/c_lutch/article/details/96730048 )

 j = dfa[txt.charAt(i)][j]
 X = dfa[par.charAt(j)][X]

3 现在重点是要理解怎么构造DFA

  • DFA的构造也是利用了dfa;
    1)首先,所有模式一开始复制的是都状态 0,因为假设你有个字符串txt:AAAA,你要从里面找ABAB,那你每次匹配失败都只要从B开始比,就是 j 只要回退到1,因为我第0位肯定是A嘛,所以有:dfa[c][j] = dfa[c][X]
    2)当匹配时会继续比较下一个字符,所以有:dfa[pat(j)][j] = j+1
    3 )其实X的更新其实是对模式自身重叠的查找。如果字符串自身重叠,就是自身和自身的开头匹配,会继续比较下一个字符,所以有:if(pat(X) == pat(q)) X++;但神奇的一步在于,X的更新可以利用已经构造好的dfa,所以有: X = dfa[pat(q)][X]
    这样,X的更新使得复制的状态不断后移,X指向X+1,j+1 复制的是X+1的模式。这样当对字符串txt 匹配失败时,但字符串如果对patternX状态匹配成功,因为复制的是X模式,所以会使 j 回退到 X 状态,使它和X状态匹配成功时的指向一样。
    比如 pattern:ABAC,和txt:ABAB匹配时在dfa[][3]处失败,但因为dfa[][3]复制的是dfa[][1]的状态,所以dfa[B][3]和dfa[B][1]一样,都是指向2;

4 然后是查找过程,这就是前面的显示回退写法

int i, j;
for(i=0, j=0; i<N && j<M; i++)
	j = dfa[txt.charAt(i)][j];
if(j == M) return i-M;
else	 	return N;

完整代码

dfa[pat.charAt(0)][0] = 1;
for(int i=1X=0; i<M; i++){
	for(int c=0; c<R; c++)
		dfa[c][i] = dfa[c][X];
	dfa[pat.charAt(i)][i] = i+1;
	X = dfa[pat.charAt(i)][X];
}
int i, j;
for(i=0, j=0; i<N && j<M; i++)
	j = dfa[txt.charAt(i)][j];
if(j == M) return i-M;
else	 	return N;
  • 完整代码结构是dfa…for for c, dfa, X…for…if return

KMP子字符串查找
显示回退写法

int i, j;
for(i=0, j=0; i<N && j<M; i++){
	if(j = -1 || pat.charAt(j) == txt.charAt(i))  j++;
	else		{i--; j = next[j];}
}
if(j == M) return i-j;
else 		return -1;

在《算法》里,KMP其实是用了显示回退写法,显示回退写法其实能更好的理解next数组与自动机的区别。自动机在匹配失败时 i 是继续前进的,next数组则需要继续在第 i 位比较,共同点就是他们都没有回退 i 。再来看教课书的写法:

int i = 0, j = 0;
while(i<N && j<M){
	if (j == -1 || pat.charAt(j) == txt.charAt(i))	{i++; j++;}
	else		j = next[j];
}
if (j == m)	return i - j;
else		return -1;

教科书里的写法其实也类似显示回退写法的,只不过把 i 的自增放入了循环体内。

最后,KMP适合查找自我重复性的模式字符串,例如111111。
KMP时间复杂度O(M+N),先遍历模式字符串为M,遍历文本字符串为N。

你可能感兴趣的:(leetcode)