KMP算法的学习
今天做题目遇到了一个关于字符串匹配的问题,似乎用传说中的KMP算法比较高效率,因为测试数据估计很BT,一般的字符串匹配算法需要不断的指针回退,效率比较低。
KMP算法所要解决的问题描述如下:
有文本串S, 比如 acabaabaabcacaabc
有搜索串P, 比如 abaabcac
要从S中找到P。
一般的解法如下:
i=0;
j=0;
len_s = strlen(S);
len_p = strlen(P);
while (i <= len_s && j <= len_p) {
if ( S[i] == S[j] ) {
++i;
++j;
} else {
i = i-j+2;
j = 0;
}
}
这个算法容易理解,S(i-j+1)S(i-j+2)...S(i) == P0P1P2..Pj,当S(i+1) != P(j+1)时,说明匹配失败,那么j回退到0, i回退到i-j+2(注意不是i-j+1,如果那样的话就死循环了), 然后继续Si跟Pj的比较。
这里面效率低的原因是,j回退的幅度太大,一下子变成0,当然, 一般情况下P是比较短的,那么回退到0影响并不是特别大,但是i回退的幅度也是很大的,一下子中间的比较成果变成0了,
KMP算法就是让i不回退,j回退一部分(最坏情况下回退全部)的算法。
如何做到这一点呢,举个例子:
S=gggabcabc
P=abcac
S3S4S5S6 = P1P2P3P4 = abca, 但是S7 != P5, 一般的算法的话,S的指针i回退到4, P的指针j回退到0。KMP算法的话, 会发现 abca =a bc a,开始跟末尾的子字符串是一样的,都是"a",也就是说,如果i不回退,让j回退到1,下次重新比较的话从S7 <=> P1 开始,因为刚才匹配失败时,S7之前的字符是S(i-1)=a, 而P(j-1)肯定=S(i-1),则P(0) = p(3)=p(j-1)=s(i-1)=s(6),这样就省略了一步比较,而i也不需要回退了。
KMP算法简单描述就是:匹配失败时,i不回退,j回退到某个位置,假设该位置为next(j) = next[j],
构造这样的一个next数组非常有意义,当next数组构造好后,我们的算法可以这样写:
i = 0;
j = 0;
len = strlen(P);
len_content = strlen(S);
find_pos = 0;
loop = 0;
while ( i < len_content && j < len ) {
loop++;
if ( S[i] == P[j] ){
if (j == 0) {
find_pos = i;
}
i++;
j++;
} else {
if ( next[j] == -1 ) {
i++;
j++;
} else {
j = next[j];
}
}
}
这里有一个next[j] = -1,这里这样写,意思是说,如果next[j]==-1,那么表示S从i这个位置为起点的话,根本无法匹配P,那么S需要往前进一位, P回退到第一位。
上面的就是KMP算法,看出来它是以next数组为基础的,重点在于提前构造P的next数组
假设P=abaabcac
可以得知next(0) = -1,就是指第一个字符都无法匹配时,让i往前进1,让j重新归0。
next(i)的涵义实际上是让在字符串P0P1..P(i-1)中找到一个最大的k,让P0P1..P(k-1) = P(i-k-1)...P(i-1), 就是指在P0P1..P(i-1)的头部跟尾部找到一样的最长字符串,则next(i) = k。
使用数学归纳法的话,假设已经有next(i) =k, next(0) = -1, 那么
next(i+1) = ?
next(i) =k 推出P0P1..P(i-1)的头部跟尾部有一样的字符串,P0P1..P(k-1) = P(i-k-1)...P(i-1), 那么如果P(k)=P(i)的话,P0P1..P(k-1)P(K) = P(i-k-1)...P(i-1)P(i),则
next(i+1) = k+1 = next(i) +1,
但是如果很不幸, P(k)!= P(i),那么问题就转变成了类似于一开始的问题,从串S中搜索T:
T=P0P1..P(k-1)P(k),
S=P0P1..P(i-1)P(i),
当在S中搜索T时,在S的尾部找到了 P0P1..P(k-1) = P(i-k-1)...P(i-1), 但是最后一步,P(k) != P(i),匹配失败,于是运用KMP算法(这里很妙的是:KMP算法以求NEXT数组为关键,而求NEXT数组的过程中, 又继续运用了KMP算法跟递推的思想),
匹配失败时,让k =next(k), 继续比较P(k)跟p(i),如果仍然不等,继续让k=next(k),直到
P(k) = p(i)或者p(k) = -1为止。
那么整个的KMP算法如下:
#include "stdio.h"
#include "string.h"
int get_next(char* str_search, char* next) {
int len;
int i;
int k;
next[0] = -1;
i = 0;
len = strlen(str_search);
k = -1;
while ( i < len ) {
if (str_search[i] == str_search[k] || k == -1) {
i++;
k++;
next[i] = k;
} else {
k = next[k];
}
}
return len;
}
void main() {
char str_content[1024];
char str_search[255];
char next[255];
int i;
int j;
int len;
int len_content;
int find_pos;
int loop;
scanf("%s", str_content);
scanf("%s", str_search);
//get the next
len = get_next(str_search, next);
//kmp
i = 0;
j = 0;
len_content = strlen(str_content);
find_pos = 0;
loop = 0;
while ( i < len_content && j < len ) {
loop++;
if ( str_content[i] == str_search[j] ){
if (j == 0) {
find_pos = i;
}
i++;
j++;
} else {
if ( next[j] == -1 ) {
i++;
j = 0;
} else {
j = next[j];
}
}
}
if (j == len) {
printf("found at %d, cost: %d loop/n", find_pos, loop);
}
}