算法思想
每当一趟匹配过程出现字符不相等时,主串指示器不用回溯,而是利用已经得到的“部分匹配”结果,将模式串的指示器向右“滑动”尽可能远的一段距离后,再继续进行比较。
举例说明
1.
首先,字符串“BBC ABCDAB ABCDABCDABDE”的第一个字符与搜索串“ABCDABD”的第一个字符进行比较。因为两字符不相等,所以搜索串向后移一位。
2.
两字符不相等,继续向后移动。
3.
重复上面操作,直到字符串中的第一个字符与搜索串中的第一个字符相等为止。
4.
接着,比较字符串与搜索串的下一个字符,得知相等。
5.
重复上面操作,直到字符串与搜索串中的字符不相等为止。
6.
这时,最容易想到的就是,将搜索串整个后移一位,再从头逐个比较。这样做,效率太差。
7.
当空格与字符D不相等时,说明前6个字符是“ABCDAB”。KMP的算法思想是,设法利用已知信息,不要把“搜索位置”移回已经比较过的位置,继续把它向后移动。
8.
针对搜索串,算出一张部分匹配表。
字符串前缀:除了最后一个字符以外,一个字符串的全部头部组合。
字符串后缀:除了第一个字符以外,一个字符串的全部尾部组合。
例如:
部分匹配值就是“前缀”和“后缀”的最长的共有元素的长度。
“A”的前缀和后缀都为空集,共有元素长度为0;
“AB”的前缀为[A],后缀为[B],共有元素长度为0;
“ABC”的前缀为[A, AB],后缀为[BC,B],共有元素长度为0;
“ABCD”的前缀为[A, AB,ABC],后缀为[BCD, CD, D],共有元素长度为0;
“ABCDA”的前缀为[A,AB,ABC,ABCD],后缀为[BCDA,CDA,DA,A],共有元素长度为1;
“ABCDAB”的前缀为[A,AB,ABC,ABCD,ABCDA],后缀为[BCDAB,CDAB,DAB,AB,B],共有元素长度为2;
“ABCDABD”的前缀为[A,AB,ABC,ABCD,ABCDA,ABCDAB],后缀为[BCDABD,CDABD,DABD,ADB,BD,D],共有元素长度为0;
9.
已知空格与字符D不相等时,根据部分匹配表得,最后一个匹配字符B的部分值为2。
移动位数=已匹配字符数-对应部分匹配值。
易得,移动位数=6-2=4.
10.
接着继续比较。
11.
因为空格与字符C不相等,这时已知匹配字符数为2("AB"),对应的部分匹配值为0。
所以移动位数=2-0=2。
12.
不相等,继续后移一位。
13.
逐个比较,直到发现字符C与字符D不相等。
移动位数=6-2=4
14.
逐个比较,直到搜索串的最后一位,发现完全匹配,于是搜索完成。
示例程序
#include<stdio.h>
#include<string.h>
#include <stdlib.h>
void makeNext(const char P[], int next[])
{
int q, k;
int m = strlen(P);
next[0] = 0;
for (q = 1, k = 0; q < m; ++q)
{
while (k > 0 && P[q] != P[k])
k = next[k - 1];
if (P[q] == P[k]){
k++;
}
next[q] = k;
}
}
void kmp(const char T[], const char P[], int next[])
{
int n, m;
int i, q;
n = strlen(T);
m = strlen(P);
makeNext(P, next);
for (i = 0, q = 0; i < n; ++i){
while (q > 0 && P[q] != T[i]){
q = next[q - 1];
}
if (P[q] == T[i]){
q++;
}
if (q == m){
printf("Pattern occurs with shift:%d\n", (i - m + 1));
}
}
}
int main( void )
{
int i;
int next[20] = { 0 };
char T[] = "BBC ABCDAB ABCDABCDABDE";
char P[] = "ABCDABD";
printf("%s\n", T);
printf("%s\n", P);
kmp(T, P, next);
for (i = 0; i < strlen(P); ++i){
printf("%d ", next[i]);
}
system("pause");
return 0;
}