KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt提出的,因此人们称它为克努特—莫里斯—普拉特操作(简称KMP算法)。KMP算法的核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是通过一个next()函数实现,函数本身包含了模式串的局部匹配信息。KMP算法的时间复杂度O(m+n) ---- 百度百科
eg.看这个之前可以先试试BF算法再来
区别:KMP和BF唯一不一样的地方就是,主串的i不会回退,而子串的j也不一定会移动到0号位置,而这同时也是KMP算法的目的
next数组
用于保存字串在某个位置匹配失败后,回退后的位置
每一个字符都需要去求出当前位置的回退位置
- KMP算法的精髓就是next数组,也就是用next[j]=k来表示,不同的j对应一个不同的k值,这个k就是将来j要移动的位置
- 而k的值是这样求的:
1.规则:找到匹配成功部分的两个相等的真子串(不包含本身),一个以下标0开始,另一个以j-1下标结尾
2.不管什么数据,next[0]=-1,next[1]=0
举一个例子:abcabc
j 0 1 2 3 4 5
a b c a b c
next[j] -1 0 0 0 1 2
这是怎么算出来的呢?
我们看规则,找到匹配成功部分的两个相等的真子串,一个以下标0开始,另一个以j-1下标结尾
首先我们知道,next[0]=-1,next[1]=0,所以前两个不用管,而当下标j走到c时,下标j-1指向的就是b,而前面没有出现过b,所以next[2]是0,而next[3]也是一样的,因为c也是第一次出现,不会找到两个c。而next[4],j-1指向a,以a开头以a结尾,只能发现两个相等的串"a",长度为1,next[4]=1;而next[5],j-1指向b,以a开头以b结尾,发现两个相等的串"ab",长度为2,故next[5]=2
两个小练习:
a b a b c a b c d a b c d e
a b c a b c a b c a b c d a b c d e
第一个:
j 0 1 2 3 4 5 6 7 8 9 10 11 12 13
a b a b c a b c d a b c d e
next[j] -1 0 0 1 2 0 1 2 0 0 1 2 0 0
第二个:
j 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
a b c a b c a b c a b c d a b c d e
next[j] -1 0 0 0 1 2 3 4 5 6 7 8 9 0 1 2 3 0
第一个比较简单,那我们就讲第二个
首先还是-1,0;j=2:然后c的前面是b,第一次出现所以为0;j=3:a的前面是c,也是第一次出现,为0;j=4:以a开头以a结尾,所以找到“a”,长度为1;j=5:以a开头以b结尾,“ab”,长度为2;j=6:以a开头以c结尾,“abc”,长度为3;j=7:以a开头以a结尾,“abca”,长度为4;j=8:以a开头以b结尾,“abcab”,长度为5;j=9:以a开头以c结尾,“abcabc”,长度为6…后面的也都是一样的
而且在计算的过程中我们会发现几个点:
①只要出现新的字符,那么这个字符的下一个字符的next[j]一定为0
②next[j]都是逐个加起来的,不会说直接从1蹦到3、4或者更大的数,而是1,2,3…
而接下来的问题就是:已知next[i]=k,如何求next[i+1]?
首先假设next[i]=k成立,那么P0…Pk-1=Px…Pi-1,由此得到:P0…Pk-1=Pi-k…Pi-1。
k-1-0=i-1-x -> x=i-k;
我们再假设:如果Pk=Pi,那么我们就可以得到P0…Pk=Pi-k…Pi,那么这个就是next[i+1]=k+1
举例
j 0 1 2 3 4 5 6 7 8 9 10
a b c a b a b c a b c
next[j] -1 0 0 0 1 2 1 2 3 4 5
当j=8时,前提:假设next[i]=k,P[0] – P[k-1]=P[i-k] – P[j-1],而在这里是P[0] – P[2],P[5] – P[7]是相等的。
现在我们还会发现,其实P[8]==P[3],也就是P[i]==p[k],所以加上这个条件:P[0] – P[k]=P[i-k] – P[j],而这就可以推出next[i+1]=k+1。而发生这个需要两个前提:①next[i]=k②P[i]=P[k]
那么有P[i]=P[k],就会有P[i]!=P[k]的情况,那这时next[i+1]=?
j 0 1 2 3 4 5 6 7 8 9 10
a b c a b a b c a b c
next[j] -1 0 0 0 1 2 1 2 3 4 5
还是用这个串来举例,我们先让j=5,那么k就要回退到2号位置,因为next[5]=2,而此时我们会发现k=2时next[2]=0,而此时j又回退到0号位,此时next[5]=next[0]=a,而此时k=0,next[i+1]=k+1,所以next[6]=0+1=1。
刚才是P[0]=a,所以我们找到了,如果此时P[0]!=a 呢?那么k就会继续回退等于-1,我们知道数组的序号不可能为负数,所以当k=-1时,此时也是next[i+1]=k+1,也就是next[i+1]=0
下面请看代码:
void GetNext(char* sub,int* next,int lenSub) {
next[0] = -1;
next[1] = 0;
int i = 2;//当前i下标
int k = 0;//前一项的k值
//这里为什么使用i-1呢,因为
while(i<lenSub)
{
if (k==-1||sub[i - 1] == sub[k])//k==-1如果放进数组就越界了,但是我们知道此时i指向的数据为0
{
next[i] = k + 1;//如果这里用next[i+1],那next[2]就算不出来了,所以把i==k改成了i-1==k
i++;
k++;//因为两个元素相等,所以都往后++
}
else
k = next[k];//k回退到当前位置
}
}
//KMP算法
int KMP(char* str, char* sub, int pos) {
assert(str && sub);
int lenStr = strlen(str);
int lenSub = strlen(sub);
if (lenStr == 0 || lenSub == 0)//如果两个中有空串的话
return -1;
if (pos >= lenStr || pos < 0)//如果一开始pos给的不正确的话
return -1;
int* next = (int*)malloc(sizeof(int) * lenSub);//创建next数组
assert(next);
GetNext(sub,next,lenSub);//next数组赋值
int i = pos;//遍历主串
int j = 0;//遍历子串
while (i < lenStr && j < lenSub)
{
if (j == -1 || str[i] == sub[j])//j=-1时由于sub[j]会越界
{
i++;
j++;//匹配成功,或者j==-1
}
else
j = next[j];//j回退
if (j >= lenSub)//找到了
return i - j;
}
return -1;//没找到
}
以上就是我对KMP算法的了解,可能还有很多不太清楚的地方,但也希望大家看完之后能够有一个大概的了解。