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))。 假设现在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 ++,否则此时没有移动模式串 )。(请结合下 文源代码看这里的分析) 下面解释一下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]]的情况下,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> int indexofsubstr(const char* str, const char* p) int main(int argc, char** argv) ***KMP 1.0********************************************************************************************************* /* * Purpose: KMP 1.0, a linear algorithm for searching pattern string in a given string!
//calculate the next array while(j > 0 && ptrn[j] != ptrn[i]) j = next[j]; if(ptrn[j] == ptrn[i]) next[i + 1] = j + 1; i++; } //KMP
} int main(int argc, char** argv) const char* p[6] = {"abcabd", "mnmna","mmx", "aaaaaaaabac", "cdecdea", "ababacm"}; int i; printf("P:%s/n", p[i]); 运行结果如下:
***KMP 2.0******************************************************************************************************** //在KMP 1.0的基础上将next代码简化了一下,其思想为当模式串和模式串失配时,利用已经求出的next值将其中一个模式串 //移动适当的距离 #include <stdio.h> #define MAX_PATTERN_LEN 1024 void print(int *a, int n) void randstr(char a[], int n) a[n - 1] = '/0'; void getnext(const char* t, int *next) // ensure strlen(t) < MAX_PATTERN_LEN } int main(int argc, char** argv) ***KMP 3.0********************************************************************************************************** //KMP 2.0中,如果模式串为aaaaaaaa这种情形时,将会退化为BF算法,所以这里对next的计算做了小小的改进 #include <stdio.h> #define MAX_PATTERN_LEN 1024 void print(int *a, int n) void randstr(char a[], int n) a[n - 1] = '/0'; void getnext(const char* t, int *next) //以下是改进的地方 int KMP_Index(const char* s, const char* t) int main(int argc, char** argv) ***KMP 4.0******************************************************************************************************** //KMP算法的一种经典实现方法! /* #define MAX_PATTERN_LEN 1024 //print the array //generate a random string a[n - 1] = '/0'; //preprocessing for KMP i++, j++; // KMP, ensure strlen(t) < MAX_PATTERN_LEN int main(int argc, char** argv)
全文完
|
KMP
最新推荐文章于 2019-11-11 23:42:41 发布
KMP算法的原理及实现【附C语言源码】(原创)
2010年12月21日 星期二 11:34