1. KMP算法是一种线性时间复杂的字符串匹配算法,它是对BF算法(Brute-Force,最基本的字符串匹配算法的)改进。
对于给的的原始串S和模式串P,需要从字符串S中找到字符串P出现的位置的索引。
BF算法的时间复杂度O(strlen(S) * strlen(T)),空间复杂度O(1)。
KMP算法的时间复杂度O(strlen(S) + strlen(T)),空间复杂度O(strlen(T))。
2. 假设现在S串匹配到i位置,T串匹配到j位置。那么总的来说,两种算法的主要区别在于失配的情况下,
对j的值做的处理:【注意,本文中的字符串下标都是从0开始计算】
BF算法中,如果当前字符匹配成功,即s[i+j] == T[j],令j++,继续匹配下一个字符;如果失配,即S[i + j] != T[j],
需要让i++,并且j = 0,即每次匹配失败的情况下,模式串T相对于原始串S向右移动了一位。 (请结合下文源代码看这里的分析)
而KMP算法中,如果当前字符匹配成功,即S[i]==T[j],令i++,j++,继续匹配下一个字符;如果如果失配,即S[i] != T[j],
需要保持i不变,并且让j = next[j],这里next[j] <=j -1,即模式串T相对于原始串S向右移动了至少1位(移动的实际位数j - next[j] >=1),
同时移动之后,i之前的部分(即S[i-j+1 ~ i]和j=next[j]之前的部分(即T[0 ~ j-1])仍然相等。显然,相对于BF算法来说,KMP移动更多的
位数,起到了一个加速的作用! (失配的特殊情形,令j=next[j]导致j==0的时候,需要将i ++,否则此时没有移动模式串)。
3.
首先说一下为什么BF算法要回溯。
aaaacefghij
aaac
如果不回溯的话就是从下一位开始比起
aaaacefghij
aaac
看到红颜色的没,就是从a 的下一位c 比起。
就漏了情况
aaaacefghij
aaac
所以必须回溯。
如下代码:
int Index(SString S, SString T, int pos) {
//返回T在S中第pos个字符之后的位置
i=pos; j=1;k=0;
while ( i< = S[0] && j< = T[0] ) {
if (S[i+k] = = T[j] ) {++k; ++j;} //继续比较后续字符
else {i=i+1; j=1; k=0;} //指针回溯到 下一首位,重新开始
}
if(j>T[0]) return i; //子串结束,说明匹配成功
else return 0;
}//Index
有特殊情况可以不回溯
abcdefghij(主串)
abcdefg(模式串)
(模式串)没有相同的才不需要回溯。
4. 分析一下KMP 算法中思想。
刚才分析了BF 算法必须要回溯。但回溯就影响了效率,回溯 是由T串本身的性质决定的,是因为T串本身有前后'部分匹配'的性质。像上面所说如果主串为abcdef这样的,大没有回溯的必要。
改进的地方也就是这里,我们从T串本身出发,事先就找准了T自身前后部分匹配的位置,那就可以改进算法。
如果不用回溯,那模式串下一个位置从哪里开始呢?
还是上面那个例子,T(模式串)为ababc,如果c失配,那就可以往前移到aba最后一个a的位置,像这样:
...ababd...
ababc
->ababc
这样i不用回溯,j跳到前2个位置,继续匹配的过程,这就是KMP算法所在。这个当T[j]失配后,j应该往前跳的值就是j的next值,它是由T串本身固有决定的,与S串(主串)无关。
5.
下面解释一下next数组的含义,这个也是KMP算法中比较不好理解的一点。
令原始串为: S[i],其中0<=i<=n;模式串为: T[j],其中0<=j<=m。
假设目前匹配到如下位置
S0,S1,S2,...,Si-j,Si-j+1...............,Si-1, Si, Si+1,....,Sn
T0,T1,...................,Tj-1, Tj, ..........
S和T的绿色部分匹配成功,恰好到Si和Tj的时候失配,如果要保持i不变,同时达到让模式串T相对于原始串S右移的话,可以
更新j的值,让Si和新的Tj进行匹配,假设新的j用next[j]表示,即让Si和next[j]匹配,显然新的j值要小于之前的j值,模式串才会是
右移的效果,也就是说应该有next[j] <= j -1。那新的j值也就是next[j]应该是多少呢?我们观察如下的匹配:
1)如果模式串右移1位(从简单的思考起,移动一位会怎么样,这样思考方法很好),即next[j] = j - 1, 即让蓝色的Si和Tj-1匹配 (注:省略号为未匹配部分)
S0,S1,S2,...,Si-j,Si-j+1...............,Si-1, Si, Si+1,....,Sn
T0,T1,...................,Tj-1, Tj, .......... (T的划线部分和S划线部分相等【1】)
T0,T1,................Tj-2,Tj-1, ....... (移动后的T的划线部分和S的划线部分相等【2】)
根据【1】【2】可以知道当next[j] =j -1,即模式串右移一位的时候,有T[0 ~ j-2] == T[1 ~ j-1]。而这两部分
敲好是字符串T[0 ~j-1]的前缀和后缀,也就是说next[j]的值取决于模式串T中j前面部分的前缀和后缀相等部分的长度
2)如果模式串右移2位,即next[j] = j - 2, 即让蓝色的Si和Tj-2匹配
S0,S1,...,Si-j,Si-j+1,Si-j+2...............,Si-1, Si, Si+1,....,Sn
T0,T1,T2,...................,Tj-1, Tj, ..........(T的划线部分和S划线部分相等【3】)
T0,T1,.............,Tj-3,Tj-2,..............(移动后的T的划线部分和S的划线部分相等【4】)
同样根据【3】【4】可以知道当next[j] =j -2,即模式串右移两位的时候,有T[0 ~ j-3] == T[2 ~ j-1]。而这两部分
也敲好是字符串T[0 ~j-1]的前缀和后缀,也就是说next[j]的值取决于模式串T中j前面部分的前缀和后缀相等部分的长度
3)依次类推,可以得到如下结论当发生失配的情况下,j的新值next[j]取决于模式串中T[0 ~ j-1]中前缀和后缀相等部分的长度,
并且next[j]恰好等于这个最大长度。
上面给出了next数组的含义,下面给出求这个数组的具体算法。
1)显然有next[0] = 0,next[1] = 0;
2)观察【1】【2】可以看到如果T[j]==T[j -1]即T[j] == T[next[j]]的情况下(一定要注意 next[j]比j 小,有助于理清思路),j+1前面字符串的前缀和后缀的相等部分长度增加了1
所以有T[j]==T[next[j]]的时候,next[j+1] = next[j ] + 1;
同样观察【3】【4】也可以看到如果T[j]==T[j-2]亦即T[j]==T[next[j]的情况下,j+1前面的字符串的前缀和后缀相等部分的长度增加了1,
所以也有T[j]==T[next[j]]的时候,next[j+1] = next[j] + 1;
综合上面的规律有当T[j] == T[next[j]]的情况下next[j+1]=next[j] + 1;
3) 当T[j] != T[next[j]]的情况next[j+1]又该等于多少呢?拿【1】【2】来说,如果此时T[j] != T[j-1],可以移动【2】对应的串,
直到【1】中的Tj等于下面【2】中对应的字符,此时就找到了j+1的最大前后缀。注意,移动的时候同样可以用到已经计算出
的next数组的值。用伪代码表示就是
k = next[j];
while(T[k] != T[j]) k = next[k];//如果不等,移动模式串
if(T[k] == T[j]) next[j + 1] = k + 1;
else next[j+1] = k;
最后给出BF算法和KMP算法(有四个版本)的源码如下。
***BF*******************************************************************************************************
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int indexofsubstr(const char* str, const char* p)
{
int lenstr = strlen(str);
int lenp = strlen(p);
int i, j;
for(i = 0; i < lenstr; ++i)
{
j = 0;
while( i + j < lenstr && j < lenp && str[i + j] == p[j]) j++;
if(j == lenp) return i;
}
return -1;
}
int main(int argc, char** argv)
{
const char* str = "abcdefghijklmmnx";
const char* p = "mmn";
printf("index:%d\n", indexofsubstr(str, p));
system("pause");
return 0;
}
***********BF1.0***********************************************************************************************
/*
* Author:puresky
* Date: 2010/12/21
* Purpose: KMP 1.0, a linear algorithm for searching pattern string in a given string!
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//print a integer array
void pnt(int a[], int n)
{
int i;
for(i = 0; i < n; ++i) printf("%d ", a[i]);
printf("\n");
}
//calculate the next array
void makenext(const char* ptrn, int *next)
{
int len = strlen(ptrn);
next[0] = 0;
next[1] = 0;
int i = 1;
while(i < len - 1)
{
int j = next[i];
while(j > 0 && ptrn[j] != ptrn[i]) j = next[j];
if(ptrn[j] == ptrn[i]) next[i + 1] = j + 1;
else next[i + 1] = j;
i++;
}
//pnt(next, len);
}
//KMP
int indexofsubstr(const char* str, const char* ptrn)
{
int lenstr = strlen(str);
int lenptrn = strlen(ptrn);
int next[1024]; //假设模式串的长度不超过1024
makenext(ptrn, next);
int i = 0, j = 0;
while(i < lenstr && j < lenptrn)
{
if(str[i] == ptrn[j])
{
i++,j++;
}
else
{
j = next[j];
// if j euqals zero, increase i by 1. Otherwize, there may be a infinite loop!
if(j == 0) i++;
}
if(j == lenptrn) return i - j; //match successfully, return the index
}
return -1;
}
int main(int argc, char** argv)
{
const char* str = "abcdecdeabghijmnmnklamnmnaxabcabcabdxababacm";
const char* p[6] = {"abcabd", "mnmna","mmx", "aaaaaaaabac", "cdecdea", "ababacm"};
int i;
for(i = 0; i < 6; ++i)
{
printf("S:%s\n", str);
printf("P:%s\n", p[i]);
printf("Index:%d\n", indexofsubstr(str, p[i]));
printf("*******************************\n");
}
system("pause");
return 0;
}
***KMP 2.0********************************************************************************************************
//在KMP 1.0的基础上将next代码简化了一下,其思想为当模式串和模式串失配时,利用已经求出的next值将其中一个模式串
//移动适当的距离
#include <stdio.h>
#include <time.h>
#include <string.h>
#include <stdlib.h>
#define MAX_PATTERN_LEN 1024
void print(int *a, int n)
{
printf("next:");
int i;
for(i = 0; i < n; ++i) printf("%d ", a[i]);
printf("\n");
}
void randstr(char a[], int n)
{
int i;
for(i = 0; i < n; i++)
a[i] = 'a' + rand() % 3;
a[n - 1] = '\0';
}
void getnext(const char* t, int *next)
{
int len = strlen(t);
int i,j;
next[0] = 0;
next[1] = 0;
i = 1;
j = 0;
while(i < len)
{
if(t[i] == t[j])
{
i++,j++;
next[i] = j;
}
else
{
if(j == 0)
{
i++;
next[i] = 0;
}
j = next[j];
}
}
print(next, len);
}
// ensure strlen(t) < MAX_PATTERN_LEN
int KMP_Index(const char* s, const char* t)
{
int next[MAX_PATTERN_LEN];
getnext(t, next);
int lens = strlen(s);
int lent = strlen(t);
int i,j;
i = 0;
j = 0;
while(i < lens && j < lent)
{
if(s[i] == t[j])
{
i++, j++;
}
else
{
if(j == 0) i++;
j = next[j];
}
if(j == lent) return i - j;
}
return -1;
}
int main(int argc, char** argv)
{
srand(time(NULL));
int times = 20;
while(--times)
{
char s[1024];
char t[1024];
randstr(s, 100);
randstr(t, 5);
printf("S:%s\nT:%s\n", s, t);
int r1 = KMP_Index(s, t);
char* r2 = strstr(s, t);
printf("%d:%s\n", r1, r2);
if(r1 == -1 && r2 == NULL || r1 == r2 - s) printf("check:TRUE ^_^\n");
else printf("check:FALSE XXXX\n");
printf("----------------------------------------------------\n");
}
system("pause");
return 0;
}
***KMP 3.0********************************************************************************************************//KMP 2.0中,如果模式串为aaaaaaaa这种情形时,将会退化为BF算法,所以这里对next的计算做了小小的改进
#include <stdio.h>
#include <time.h>
#include <string.h>
#include <stdlib.h>
#define MAX_PATTERN_LEN 1024
void print(int *a, int n)
{
printf("next:");
int i;
for(i = 0; i < n; ++i) printf("%d ", a[i]);
printf("\n");
}
void randstr(char a[], int n)
{
int i;
for(i = 0; i < n; i++)
a[i] = 'a' + rand() % 3;
a[n - 1] = '\0';
}
void getnext(const char* t, int *next)
{
int len = strlen(t);
int i,j;
next[0] = 0;
next[1] = 0;
i = 1;
j = 0;
while(i < len)
{
if(t[i] == t[j])
{
i++,j++;
//以下是改进的地方
if(t[i] == t[j])
next[i] = next[j];
else
next[i] = j;
}
else
{
if(j == 0)
{
i++;
next[i] = 0;
}
j = next[j];
}
}
print(next, len);
}
int KMP_Index(const char* s, const char* t)
{
int next[MAX_PATTERN_LEN];
getnext(t, next);
int lens = strlen(s);
int lent = strlen(t);
int i,j;
i = 0;
j = 0;
while(i < lens && j < lent)
{
if(s[i] == t[j])
{
i++, j++;
}
else
{
if(j == 0) i++;
j = next[j];
}
if(j == lent) return i - j;
}
return -1;
}
int main(int argc, char** argv)
{
srand(time(NULL));
int times = 20;
while(--times)
{
char s[1024];
char t[1024];
randstr(s, 100);
randstr(t, 5);
printf("S:%s\nT:%s\n", s, t);
int r1 = KMP_Index(s, t);
char* r2 = strstr(s, t);
printf("%d:%s\n", r1, r2);
if(r1 == -1 && r2 == NULL || r1 == r2 - s) printf("check:TRUE ^_^\n");
else printf("check:FALSE XXX\n");
printf("----------------------------------------------------\n");
}
system("pause");
return 0;
}
**************KMP 4.0*************************************************************************************************
//KMP算法的一种经典实现方法!
/*
* Author:puresky
* Date: 2010/12/22
* Version: KMP 4.0,
* Refer to:Handbook of Exact String-Matching Algorithms, Christian Charras & Thierry Lecroq
*/
#include <stdio.h>
#include <time.h>
#include <string.h>
#include <stdlib.h>
#define MAX_PATTERN_LEN 1024
//print the array
void print(int *a, int n)
{
printf("next:");
int i;
for(i = 0; i < n; ++i) printf("%d ", a[i]);
printf("\n");
}
//generate a random string
void randstr(char a[], int n)
{
int i;
for(i = 0; i < n; i++)
a[i] = 'a' + rand() % 3;
a[n - 1] = '\0';
}
//preprocessing for KMP
void preKMP(const char* t, int next[], int len)
{
int i,j;
next[0] = -1; //Attention: next[0] equals -1 other than 0
i = 0;
j = -1;
while(i < len)
{
while(j > -1 && t[j] != t[i])
j = next[j];
i++, j++;
if(t[j] == t[i])
next[i] = next[j];
else
next[i] = j;
}
print(next, len);//output the 'next' array
}
// KMP, ensure strlen(t) < MAX_PATTERN_LEN
int KMP(const char* s, const char* t)
{
int next[MAX_PATTERN_LEN];
int lens = strlen(s);
int lent = strlen(t);
preKMP(t, next, lent);
int i,j;
i = j = 0;
while(i < lens)
{
while(j > -1 && t[j] != s[i])
j = next[j];
i++, j++;
if(j >= lent) return i - j;
}
return -1;
}
int main(int argc, char** argv)
{
srand(time(NULL));
int times = 20;
while(--times)
{
char s[1024];
char t[1024];
randstr(s, 100);
randstr(t, 5);
printf("S:%s\nT:%s\n", s, t);
int r1 = KMP(s, t);
char* r2 = strstr(s, t);
printf("%d:%s\n", r1, r2);
if(r1 == -1 && r2 == NULL || r1 == r2 - s) printf("check:TRUE ^_^\n");
else printf("check:FALSE XXX\n");
printf("----------------------------------------------------\n");
}
system("pause");
return 0;
}
转自: http://hi.baidu.com/gropefor
http://blog.csdn.net/guocai_yao/article/details/4089172