KMP

QUESTION:

有一个文本串S,和一个模式串P,现在要查找P在S中的位置,怎么查找呢?

ANSWER:

KMP

step1:(真)前缀&(真)后缀

char pre suc pre \(\cap\) suc
abc a,ab c,bc \(\varnothing\)
abcba a,ab,abc,abcb a,ba,cba,bcba a
ababa a,ab,aba,abab a,ba,aba,baba a,aba
abcab a,ab,abc,abca b,ab,cab,bcab ab

\(nxt[i]\)\(P[0]\)~\(P[i-1]\)\(pre \cap suc\) 中最长元素的长度

P a b a b a b c a
nxt -1 0 0 1 2 3 4 0

好了,解释清楚这个表是什么之后,我们再来看如何使用这个表来加速字符串的查找

e.g.

S="ababababca"
P="abababca"


如果在 j 处字符不匹配,那么由于前边所说的 \(P\) 字符串 \(nxt\) 的性质,

\(S[i-nxt[j]]\) ~ \(S[i-1]=P[0]\) ~ \(P[nxt[j]-1]\) (伪代码)

这是因为主字符串在 i 位失配,也就意味着

\(S[i-j]\) ~ \(S[i-1]=P[0]\) ~ \(P[j-1]\) (伪代码)

在这个例子中就是

\(S[i-j]\) ~ \(S[i-1]=P[0]\) ~ \(P[j-1]="ababab"\) (伪代码)

\(pre \cap suc\) 的最长元素为 \("abab"\) ,长度为 \(4\)

所以就可以断言,(a)图中两个灰色部分是相同的,即长度为 \(4\) 的后缀与前缀相同。

这样一来,我们就可以将灰色字符段的比较省略掉。

具体的做法是:(即变为(b)图)

i=i;
j=nxt[j];

具体函数:

int KMP() {
	int i = 0;
	int j = 0;
	while(i < S.size() && j < P.size()) {
		if (j == -1 || S[i] == P[j]) 
			i++;
			j++;
		} else {
			j = nxt[j];
		}
	}
	if (j == P.size())
		return i - j;
	else
		return -1;
}

但是怎么求nxt?

SO EASY

\(nxt[i]\) 的过程完全可以看成字符串匹配的过程,即

newS=P;
newP=P.pre;//P的前缀皆可

一旦字符串匹配成功,那么当前的 \(nxt[i]\) 值就是 \(len(\)匹配\()\)

具体来说,就是从\(P\)的第一位(注意,不包括第0位)开始对自身进行匹配运算。





void getNxt()
{
	nxt[0] = -1;
	int i = 0, j = -1;

	while (i < P.size())
	{
		if (j == -1 || P[i] == P[j])
		{
			i++;
			j++;
			nxt[i] = j;
		}	
		else
			j = nxt[j];
	}
}

D.E.Knuth
J.H.Morris
V.R.Pratt
三人联手打造KMP
KMP解决字符串匹配问题
(n为原串长度,m为匹配串长度)
蛮力算法:O(nm)
KMP:O(n+m)
KMP.png
若在匹配中在图中蓝色位置失配
则匹配串要右移(假设原串与蓝框不动)
因为目标是让匹配串全部匹配,所以右移后让匹配串全部匹配的必要条件图中绿框串相等(不相等的话直接GG)
为避免回溯,绿框大小尽可能大(但不碰蓝框),即右移位移少(保险)

理解如何求出\(nxt[i]\)

顺次扫描模式串P中每个元素,求出\(nxt[i]\)
当遍历到\(i+1\)时,\(nxt[1]\)\(nxt[i]\)均已经求出
如图\(O(Kn)\)求出即可(\(K\)为常数,图中相同颜色的矩形和相同的符号分别代表相同的子串和相同的字符)

posted @ 2021-03-14 11:09  ShaoJia  阅读(54)  评论(0编辑  收藏  举报