KMP字符串匹配算法

KMP字符串匹配算法

常见的 \(O(n*m)\) 暴力算法

for (int i=1;i<=n;i++){
	int j,k;
	for (j=i,k=1;k<=m;j++,k++){
		if (s1[j]!=s2[k])
			break;
	}
	if (k==m) cout<<i;
}

最外层用 \(i\) 枚举每个字符串首的位置,内层用两个指针 \(j,k\) 分别指向 \(s1,s2\)

如果 s1[j]!=s2[k] ,说明在该位置上不能匹配,则退出内层循环,将 \(i\) 后移

如果内层循环出来后 k==m ,则可以匹配到相同的字符串,输出此时 \(i\) 的位置

这种做法可以简单理解,但是时间复杂度非常的高


改进这种做法,发现到每次匹配的过程中我们都会舍弃掉已经匹配过的字符,实际上这些字符可以提供一定的信息

s1:aabaabaabaabaaaa
s2:aabaabaaaa


*图引自董晓算法

模拟一下过程,当 i=1,j=k=9 时不能匹配,暴力做法是将 \(i\) 后移,在下一个字符开始重新遍历

但是可以发现,i=3 时可以直接匹配到 j=k=9 ,这样就节省了 i=2 遍历的时间


我们可以打一张表来映射 j,k 的位置与 i 移动的位置的关系

int ne[N];
ne[1]=0;
for (int i=2,j=0;i<=m;i++){
	while (s2[i]!=s2[j+1]&&j) j=ne[j];
	if (s2[i]==s2[j+1]) j++;
	ne[i]=j;
}

双指针对 \(s_2\) 进行操作,用来计算 ne[i] ,即匹配到 i 位置不同后,指针的移动距离

在预处理过程中,\(i\) 指针用来遍历 \(s_2\) 串,\(j\) 指针用来计算最长相同前后缀的长度

如果 \(i,j+1\) 指向的字符不同,那么可以通过之前的结论类似递推的方式计算

while (s2[i]!=s2[j+1]&&j) j=ne[j];//如果j+1指向的元素与i不同,那么考虑从j位置上的最长相同前后缀转移

最终要是都不能匹配,则 \(ne[i]=0\) ,表示不存在最长相同前后缀,应用到匹配过程中,即不能跳跃移动最外层的指针

预处理的时间复杂度为 \(O(m)\)


匹配过程中,写法也很类似

for (int i=1,j=0;i<=n;i++){
	while (s1[i]!=s2[j+1]&&j) j=ne[j];
	if (s1[i]==s2[j+1]) j++;
	if (j==m) cout<<i-m+1<<endl;
}

同样是双指针,由于只会遍历 \(s_1\) 中的每个元素最多一次,所以匹配的时间复杂度为 \(O(n)\)

我们这里考虑移动指向 \(s_2\) 的指针 \(j\) 的位置,每次利用 ne 进行回跳

*图引自董晓算法

posted @ 2025-03-28 17:32  才瓯  阅读(62)  评论(0)    收藏  举报