KMP及其改进算法

本文主要讲述KMP已经KMP的一种改进方法。若发现不正确的地方,欢迎交流指出,谢谢!

KMP算法的基本思想:

KMP的算法流程:

每当一趟匹配过程中出现字符比较不等时,不需回溯 i 指针,而是利用已经得到的部分匹配的结果将模式向右滑动尽可能远的一段距离后,继续进行比较。

S为目标串T为模式串,设 i 指针和 j 指针分别指示目标串和模式串中正待比较的字符。

开始时,令i=0,j=0。如果Si==Tj,则使i和j的值分别增加l;反之,i不变,j的值退回到j=next[j]的位置(即模式串右滑),然后再对Si和Tj进行比较。依次类推,直到出现下列两种情况之一

1.j值退回到某个j=next[j]时,有Si==Tj,则指针的值各增加1后,再继续匹配;

2.j值退回到 j=-1,此时令指针的值各增加1,也即下一次对Si+1和T0进行比较。

模式匹配KMP算法的时间复杂度为O(m+n),只有当模式与珠串之间存在许多“部分匹配”的情况下显得比朴素字符匹配算法快得多。但是KMP算法最大的特点就是指示主串的指针不需要回溯,整个过程中,对主串仅需从头至尾扫描一遍,这对处理从外设输入的庞大文件很有效,可以边读入边匹配,而无需回头重读。

跟朴素匹配算法的主要差异:

在于当Si != Tj的时候,朴素算法采用的是将Tj往前推一格,然后将j置为0,重新进行匹配,而KMP采用的方法是将j置为next[j],然后再匹配。

很显然,这里的next[j]是算法的核心

下面是next[j]的计算方法,以及代码的实现:

  1. void get_nextval( const char *s, int *nextval)
  2. {
  3. int len = strlen(s);
  4. int i = 0, j = -1;
  5. nextval[0] = -1;
  6. while( i < len-1 ){
  7. if( j == -1 || s[i] == s[j] ){
  8. ++i;
  9. ++j;
  10. if( s[i] != s[j] ){
  11. nextval[i] = j;
  12. }else{
  13. nextval[i] = nextval[j];
  14. }
  15. }else{
  16. j = nextval[j];
  17. }
  18. }
  19. return ;
  20. }

得到了next[j]之后,KMP算法的实现就很简单了,按照上面KMP的算法流程,可以很快写出代码:

  1. //s为匹配串
  2. //t为主串
  3. int kmp( const char *s, const char *t )
  4. {
  5. int k = -1;
  6. int nextval[N] = {0};
  7. int s_len = strlen(s);
  8. int t_len = strlen(t);
  9. get_nextval( s, nextval );     //get_nextval[]
  10. cout<<"nextval:"<<endl;
  11. for( k = 0; k < s_len; k++)
  12. cout<<nextval[k]<<" ";
  13. cout<<endl;
  14. int i = 0, j = 0;
  15. while( i < t_len && j < s_len ){
  16. if( j == -1 || t[i] == s[j] ){
  17. i++;
  18. j++;
  19. }else{
  20. j = nextval[j];
  21. }
  22. }
  23. if( j >= s_len ){
  24. return i-s_len;
  25. }else{
  26. return -1;
  27. }
  28. }

下面给出一个KMP的实现及测试代码:

  1. #include <iostream>
  2. using namespace std;
  3. #define N 100
  4. void get_nextval( const char *s, int *nextval);
  5. int kmp( const char *s, const char *t );
  6. //s为匹配串
  7. //t为主串
  8. int kmp( const char *s, const char *t )
  9. {
  10. int k = -1;
  11. int nextval[N] = {0};
  12. int s_len = strlen(s);
  13. int t_len = strlen(t);
  14. get_nextval( s, nextval );     //get_nextval[]
  15. cout<<"nextval:"<<endl;
  16. for( k = 0; k < s_len; k++)
  17. cout<<nextval[k]<<" ";
  18. cout<<endl;
  19. int i = 0, j = 0;
  20. while( i < t_len && j < s_len ){
  21. if( j == -1 || t[i] == s[j] ){
  22. i++;
  23. j++;
  24. }else{
  25. j = nextval[j];
  26. }
  27. }
  28. if( j >= s_len ){
  29. return i-s_len;
  30. }else{
  31. return -1;
  32. }
  33. }
  34. void get_nextval( const char *s, int *nextval)
  35. {
  36. int len = strlen(s);
  37. int i = 0, j = -1;
  38. nextval[0] = -1;
  39. while( i < len-1 ){
  40. if( j == -1 || s[i] == s[j] ){
  41. ++i;
  42. ++j;
  43. if( s[i] != s[j] ){
  44. nextval[i] = j;
  45. }else{
  46. nextval[i] = nextval[j];
  47. }
  48. }else{
  49. j = nextval[j];
  50. }
  51. }
  52. return ;
  53. }
  54. int main()
  55. {
  56. char s[N], t[N];
  57. while( cin>>s >>t ){
  58. int i = 0;
  59. i = kmp( s, t );
  60. cout <<"ans = " <<i <<endl;
  61. }
  62. return 0;
  63. }

测试如下:

KMP及其改进算法

KMP模式匹配问题的改进思想和方法

KMP的不足之处:

通过观察,我们可以在原有的KMP算法中,发现一个不足的地方,也就是我们将要改进的地方就是,因为,子串的出现是随机的,如果子串在主串出现的位置靠后的时候,KMP算法实在显得比较低效。现在我们给出一个例子来说明问题。

主串为:aspowqeursoolksnkhiozbgwoinpweuirabaac

子串为:abaac

容易看出,子串要到最后才会得到匹配,因此,我们提出我们的思想——从主串的首和尾同时进行匹配,那样,就可以提高算法的效率,并且,除了在这个方面提高算法效率以外,我们还想到,当 m >>n,,n>>0的时候(m为主串长度,n为子串长度),并且子串并没有在主串中出现的话,那么,在改进算法中,我们将不需要比较到最末才判断是否存在匹配的子串,而是通过剩下的字符数,来判断是否存在足够的字符与子串匹配,如果不足的话,那样就不存在,否则就继续匹配下去。

如何实现从主串末尾想串头开始匹配呢?

我们这里有两个方案:第一个方案是,把子串逆转,然后沿用旧的KMP算法中的next函数求出其逆转后的子串的next值,再用以进行匹配;第二个方案就是,不需要把子串逆转,而是采用一个新的next函数直接求出其逆转后的next值。

第一二个方案比较后,我们选择第二个方案。因为,在 n>>0的时候,明显地,在把子串逆转的时候同时需要多一个字符串来存放,并且,在不同的匹配都需要一个新的字符串,这样就大大地浪费空间了,除此之外,第一个方案至少要做遍历子串两次,而第二个方案只需要遍历子串一次就可以了。所以我们决定采用构建一个新的next函数来求出其逆转后的子串next值。

我们新的next函数的思想就是,把末字符看成是首字符,然后,仿照KMP算法中的next函数的实现方式最终实现的。现在,我们给出实现的新的next函数:

  1. void nextres( char* p, int *next, int n )
  2. {
  3. int i, j, k;
  4. i = n-1, j = -1;
  5. *next = -1;
  6. k = n;
  7. while( i > 0 ){
  8. if( j == -1 || *(p+i) == *(p+k-j-1)){
  9. i--, j++;
  10. if( *(p+i) != *(p+k-j-1) )
  11. *(next+n-i-1) = j;
  12. else
  13. *(next+n-i-1) = *(next+j);
  14. }
  15. else
  16. j = *(next+j);
  17. }
  18. }

在得到逆转后的子串的next函数后,我们就可以进行串的匹配了。其基本思路同原KMP算法,下面我们就给出匹配过程的实现:

  1. int march( char* mainhead, char* head, int mainlen, int lenth, int *next1, int *next2 )
  2. {
  3. int i, j, k, l, m;
  4. i = 0, j = 0, k = mainlen-1, m = lenth-1, l = 0;
  5. while( (m>0 && j<lenth) || lenth == 1 ){
  6. if( lenth == 1 && ( *(mainhead+i) == *(head+j) ||
  7. *(mainhead+k) == *(head+m)))
  8. return 1;
  9. if( j == -1 || *(mainhead+i) == *(head+j))
  10. i++, j++;
  11. else
  12. j = *(next1+j);
  13. if( l == -1 || *(mainhead+k) == *(head+m)){
  14. k--;
  15. l++;
  16. m = lenth==2?m-l:m-1;
  17. }else{
  18. l = *(next2+1);
  19. if( l != -1 )
  20. m = m-l;
  21. else
  22. m = lenth-1;
  23. }
  24. if( k-i < m-j )
  25. return 0;
  26. }
  27. if( m <= 0 || j >= lenth)
  28. return 1;
  29. else
  30. return 0;
  31. }

新的KMP算法在某种程度上的确可以提高模式匹配的效率。除此以外,新的模式匹配算法还能提早结束不必要的匹配。

from: http://blog.csdn.NET/cyh_24/article/details/8162436

 
0
上一篇:ie中弹出框中元素的定位


下一篇:Activiti 基本操作之“受理人变量”