KMP算法原理

来源:互联网 发布:计算机二级c语言真题 编辑:程序博客网 时间:2024/04/29 06:39

1. 摘要

这篇博客系统地介绍了模式匹配(KMP)算法的基本原理。在字符串搜索过程中,传统的暴力搜索方法在失配时,必须回溯重新搜索。回溯产生了时间复杂度高的问题,降低了字符串搜索的效率。针对暴力搜索这一缺点,Donald Knuth、Vaughan Pratt、James H. Morris三人于1977年提出了KMP算法。KMP算法在搜索发生失配时,将模式串向右滑动到某个位置重新开始匹配而不是回溯,提高了搜索效率。经过分析,KMP算法的时间复杂度为O(m+n),暴力搜索算法的时间复杂度为O(mn),KMP算法的搜索效率远远高于暴力搜索算法。

2. 暴力搜索算法原理

暴力搜索算法从文本串和模式串首部逐个地进行匹配,当发生失配时,文本串回溯到文本串本次比较开始处的下一个位置,而模式串回溯到首位置。

暴力搜索算法步骤:

说明:假设要在文本串text中查找模式串pattern。文本存储在text的0~n-1的单元中,模式存储在pattern的0~m-1中。

输入: 串text和pattern

输出:如果在text中找到pattern,则返回pattern在text中的起始位置,否则,返回-1

(1)初始化index, i和j为0;index是text和pattern比较的起始位置,i扫描text,j扫描pattern;

(2)当i<n且j<m且n-i>=m时,

若text[i] =pattern[j],则i和j都增1;

否则,i 回溯到 i-j+1, j回溯到0,并且index增1。

(3)如果j不等于m,则index等于-1;

(4)返回index。

暴力搜索算法代码:

int violent_kmp(char* text, char* pattern){int i=0;int j=0;int index=0;int n = strlen(text);int m = strlen(pattern);while(i<n && j<m && n-i>=m){if(text[i] == pattern[j]){i++;j++;}else{i = i - j + 1;j = 0;index += 1;}}if(j != m)index = -1 ;return index;}

3. KMP算法原理

考虑两个普通的文本串text和pattern。text串用{t0, t1, ..., tn}表示,pattern串用{p0, p1, ..., pm}表示。则text[i]=ti,pattern[j]=pj。在text中搜索pattern过程中,进行到这样一个状态:pattern[j]前j个字符和text[i]前i个字符一一匹配,但是pattern[j]与text[i]比较时失配。如下图1所示。

                                                                                                 


kmp图1

                                             图1 模式串pattern在位置 j 失配的状态


针对暴力搜索算法的缺点,要避免回溯,必须将pattern向右移动,让text[i]和pattern[k]开始匹配,这里k<j。图2 展示了pattern串在位置j处失配后,向右滑动在位置k处恢复搜索的状态。

kmp图2

                                                                                                                                             图2 pattern串向右滑动后匹配状态


从图2 明显可以看出,为了让text[i]和pattern[k]将搜索继续下去,pattern前的k个字符必须和pattern[k]之前的k个字符相等,并且pattern[k]不能等于pattern[j]。把这个k值记为next[j]。next[j] 是pattern中通过比较pattern[k]和text[[i]从而可以继续搜索下去的位置。也就是说,将pattern向右滑动,使得pattern[k]和text[i]对齐,并从该点继续搜索。如果没有这样的k存在,令next[j]=-1,让搜索从pattern[0]和text[i+1]开始。(这里相当于将pattern向右滑动来讲不存在的位置-1与text[j]对齐,然后恢复搜索)


假设我们已经得到next数组,那么KMP算法步骤如下:

说明:假设要在文本串text中查找模式串pattern。文本存储在text的0~n-1的单元中,模式存储在pattern 的0~-m-1中。

输入: 串text和pattern

输出:如果在text中找到pattern,则返回pattern在text中的起始位置,否则,返回-1

               (1)初始化index, i和j为0;index是text和pattern比较的起始位置,i扫描text,j扫描pattern;

(2)当i<n且j<m时,

若text[i] =pattern[j],则i和j都增1;

否则,做一下工作:

将pattern向右滑动合适的距离, index=index+j-next[j];

如果next[j]不等于-1, j = next[j];否则 j等于0,i增1。

(3)如果j<m,则index等于-1;

(4)返回index。

4. Next数组

KMP算法的关键在于如何得到next数组。根据图2,我们可以直观地看出,next[j]就是pattern中最长的并且和pattern[j]前k个字符匹配的前缀pattern[0],pattern[1],...,pattern[k-1],而且pattern[k]不等于pattern[j]。事实上,next中保存的是模式串中所有有相同前缀和后缀的子串的前缀后缀长度。这些前缀后缀相同的子串可以将模式串pattern在其自身的一个副本上滑动来得到。再次考虑一个普通形式的模式串{p0, p1, ..., pm-1}。将它与自身的副本进行模式匹配。如图3所示,显然next[0]=-1,因为pattern[0]没有前缀。现在,如果next[0, next[1], ..., next[j-1]已经得到,可以使用这些值来计算next[j]。


KMP3

                               图3 模式串与其自身的一个副本进行模式匹配

如果前缀长度为k,并且pattern[k]不等于pattern[[j], 那么根据next的定义,next[j] = k。如果pattern[k]等于pattern[[j],显然next[j]  = next[k]。根据以上分析,得到计算next的方法如下:

输入:串pattern

输出:数组next

(1)初始化next[[0]=-1, k为-1,j为0;

(2)当j<m时,

如果k不等于-1并且pattern[k]不等于pattern[j],k=next[k];

k和j增1;

如果pattern[j]==pattern[j],则next[j]=next[k];否则,next[j] = k。

计算next的代码如下:

void GetNext(char* pattern, int next[]){assert(next);next[0] = -1;int k = -1;int j = 0;int m = strlen(pattern);while(j<m){if(k != -1 && pattern[k] != pattern[j])k = next[k];k++;j++;if(pattern[j] == pattern[k])next[j] = next[k];elsenext[j] = k;}}

有了next数组,就可以得到KMP的完整算法。KMP完整的C代码如下:

int KMP(char* text, char* pattern){int n = strlen(text);int m = strlen(pattern);int index  = 0; //index 是ptn在text中的其实位置int i = 0;int j = 0;int* next = (int*)malloc(m*sizeof(int));GetNext(pattern, next);while(i<n&& j<m){if(text[i] == pattern[j]){i++;j++;}else{index = index + j - next[j];if(-1 != next[j])j = next[j];else{j = 0;i++;}}}if(next){free(next);next = NULL;}if(j == m)return index;elsereturn -1;}

5. 总结

KMP算法的关键在于求解next数组。借助next数组,可以避免暴力搜索算法中在失配时的回溯,降低算法时间复杂度,提高搜索效率。分析得知,暴力搜索算法的时间复杂度为O(mn), 而KMP为O(m+n)。明显O(mn)>>O(m+n)。在理解KMP的过程中,首先应该从暴力搜索算法开始,理解为什么暴力算法会产生回溯和如何避免回溯问题。对于next数组,需要深刻理解“具有相同前缀和后缀的子串”这一概念。动手一步步迭代算法的过程是最好的。

6. 参考文献

[1] http://write.blog.csdn.net/postedit?ref=toolbar<http://write.blog.csdn.net/postedit?ref=toolbar>

[2] C++数据结构导引

0 0