跳至主要內容

从暴力枚举到KMP算法

Zxgaer大约 3 分钟KMP算法字符串

例1 给定两个字符串 S1S1S2S2S2S2S1S1的子串,输出S2S2S1S1中所有的起始下标

输入样例

cookcow
cow

输出样例

4

暴力枚举(朴素算法)

从S2第一位开始匹配S1中的每一位,直到整个S2匹配上,输出结果

i=0,j=0i=0,j=0 c匹配c
i=0,j=1i=0,j=1 o匹配o
i=0,j=2i=0,j=2 o不匹配w

cookcow
cow

i=1,j=0i=1,j=0 o不匹配c

cookcow
cow

i=2,j=0i=2,j=0 o不匹配c

cookcow
cow

i=3,j=0i=3,j=0 k不匹配c

cookcow
cow

i=4,j=0i=4,j=0 c匹配c
i=4,j=1i=4,j=1 o匹配o
i=4,j=2i=4,j=2 w匹配w

cookcow
cow

S1S1串长度为m,S2S2串长度为n,最好情况下S1S1开头就对上S2S2,此时时间复杂度为O(n)O(n),最坏情况下S1S1末尾对上S2S2,且S1S1串每隔n1n-1字符就与S2S2不匹配,则时间复杂度为O((nm+1)m)O((n-m+1)*m)

代码如下:

int findSubstr(string stra,string strb) {
    int i=0,j=0;
    while(i<stra.size()&&j<strb.size()) {
        if(stra[i]==strb[j]) i++,j++;
        else i=i-j+1,j=0;
    }
    if(j>=strb.size()) return i-j;
    else return -1;  //-1是匹配失败的标志
}

下面来看看暴力枚举的缺点

注意到,当主串的co匹配子串的co,而主串的o与子串的w失配时,主串检索下标增加一,而我们发现,如果[coo]kcow与[cow]匹配,那么c[ook]cow绝对不可能与[cow]匹配的,因为两个字符串的首字母不相同,更好的思路是当发现cc不能匹配的时候,从主串下一次出现cc的位置开始匹配,进而推广到前缀和后缀

从暴力匹配到KMP算法

暴力匹配的缺点就是主串和子串需要一个一个字符判断,为了解决这个问题,KMP算法引入了前缀表,通过预处理子串,实现遇到失配情况时合理回退

前缀表的计算方法大致如下:
设字符串S=zxxzxzgS=zxxzxzg
子串S1=zS1=z,无前缀和后缀,则d[1]=0d[1]=0
子串S2=zxS2=zx,无相同前缀和后缀,则d[2]=0d[2]=0
子串S3=zxxS3=zxx,无相同前缀和后缀,则d[3]=0d[3]=0
子串S4=zxxzS4=zxxz,相同前后缀为zz,则d[4]=1d[4]=1
子串S5=zxxzxS5=zxxzx,相同前后缀为zxzx,则d[5]=2d[5]=2
子串S6=zxxzxzS6=zxxzxz,相同前后缀为zz,则d[6]=1d[6]=1
子串S7=zxxzxzgS7=zxxzxzg,无相同前缀和后缀,则d[7]=0d[7]=0

void getnext(string p) {
	int lenp=p.size();
	nextt[0]=-1;
	int k=-1,j=0;
	while(j < lenp-1) {
		if(k==-1||p[j]==p[k]) {
			j++,k++;
			if(p[j]!=p[k]) nextt[j]=k;
			else nextt[j]=nextt[k];
		}
		else k=nextt[k];
	}
	return;
}

int KMP(string s,string p) {
	int i=0,j=0;
	int lens=s.size(),lenp=p.size();
	while(i<lens&&j<lenp) {
		if(j==-1||s[i]==p[j]) j++,i++;
		else j=nextt[j];
	}
    return j==lenp;
}

KMP算法,百度搜索,细心体会(byd一本通

上次编辑于:
贡献者: Zxgaer