KMP模式匹配

字符串从下标0开始存
求模式串s是否是主串p的一个子串,若是,输出在主串中的下标

暴力匹配

  • 一个指针i记录匹配过程中主串中的下标,一个指针j记录匹配过程中子串中的下标,一个指针k记录主串中开始匹配的下标
  • 当从k开始匹配的子串匹配失败,k加一,再以k为起点开始匹配
int simple(const string& str,const string& substr){
	int k=0;
	int i=k;
	int j=0;
	while(i<str.length()&&j<substr.length()){
		if(str[i]==substr[j]){
			i++,j++;
		}
		else {    //子串匹配失败,从k+1开始重新匹配
			j=0;
			k++;
			i=k;
		}
	}
	if(j==substr.length()){    //substr遍历完了,匹配成功
		return k;
	}
	else return -1;    //没找到返回-1
}

时间复杂度\(O(L_{1}L_{2})\)

KMP算法

本质是对暴力的优化
暴力匹配中的i在失配后需要回溯到上一个匹配起点k的下一个位置,重新开始匹配

思路

i失配时:从k+1开始重新匹配(暴力)

  1. 从某个大于k的下标到上一次失配的下标i的上一个字符都匹配上了,又会再次匹配i位置
  2. k+1i的上一个下标都失配,又会再次从i开始匹配

所以最终还是会走到i位置

优化:i不回溯,让j回溯到一个合适的位置

最长相同前后缀:一个字符串的前缀子串和后缀子串相同,且符合条件的子串最长
比如abcdhabc的最长相同前后缀是abc

假设在主串的i,模式串的j失配,此时模式串的下标0`j-1`和主串的下标`i-j`i-1是完全相同的,假设这个子串的最长相同前后缀长度为len,模式串的下标0`len-1`和主串的下标`i-len`i-1是完全相同的,所以j该回溯的位置是len,该位置与主串p无关,所以需要求出模式串s每个前缀子串的最长相同前后缀的长度

用数组next记录next[j]应该回溯的位置,也就是模式串的下标0~j-1的最长相同前后缀的长度,也就是模式串的长度为j的前缀子串的最长相同前后缀的长度

求next数组

动态规划思想

next[0]=-1,next[1]=0,-1作为非法值

假设已知0~j-1next值,要求next[j]
next[j-1]=a,即模式串s0~j-2的前缀子串的最长相同前后缀的长度为a,此时需要比较(匹配)j-1位置和a位置,相当于模式串自己匹配自己

  1. s[a]==s[j-1],则模式串s0~j-1的前缀子串的最长相同前后缀的长度为next[j-1]+1,即a+1
  2. s[a]!=s[j-1],相当于失配,a回溯到next[a],重新匹配

实现

void getNext(){
	nex[0]=-1;
	nex[1]=0;
	int j=2;    //要求nex的下标
	int a=nex[1];
	while(j<ls){
		if(a==-1||s[j-1]==s[a]){
			s[j]=a+1;
			j++,a++;
		}
		else {    //匹配失败,需要a回溯
			a=nex[a];
		}
	}
}

int KMP(string& p,string& s){
	getNext();
	int lp=p.length();    //主串长度
	int ls=s.length();    //模式串长度
	int i=0,j=0;
	while(i<lp&&j<ls){
		if(j==-1||p[i]==s[j]){
			i++,j++;
		}
		else {    //失配
			j=nex[j];
		}
	}
	if(j>ls)cout<<i-ls<<endl;
	else cout<< -1 <<endl;
}

时间复杂度\(O(L_{1}+L_{2})\)

最短相同前后缀

有三个长度分别为lsltlr的字符串str,且 \(ls<lt<lr\)

结论:

  1. st的相同前后缀,tr的相同前后缀,则sr的相同前后缀
  2. sr的相同前后缀,tr的相同前后缀,则st的相同前后缀

要找长度为a的前缀子串的最小相同前后缀的长度

长度为a的前缀子串最长相同前后缀长度为nex[a],依次迭代,直到nex[a]为0或-1,此时的a就是要求的最短相同前后缀

int solve(int a){
	while(nex[a]!=0&&nex[a]!=-1)a=nex[a];
	return a;
}

循环字符串

循环字符串s由字符串r循环复制得到,且r不为循环字符串
比如:s="abcabcabcabc",r="abc"

则循环字符串s的最小循环节长度(r的长度)为n-nex[n]

任何一个字符串都可以看成是循环字符串的前缀子串

PixPin_2026-04-18_16-33-02

所以若字符串s是某个循环字符串的前缀子串,这个循环字符串的最小循环节也为n-nex[n]

posted @ 2026-04-18 16:35  mofei1116  阅读(9)  评论(0)    收藏  举报