算法思想
每当一趟匹配过程出现字符不相等时,主串指示器不用回溯,而是利用已经得到的“部分匹配”结果,将模式串的指示器向右“滑动”尽可能远的一段距离后,再继续进行比较。
举例说明
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; }