`

KMP算法详解及各种应用

阅读更多

KMP算法详解:
KMP算法之所以叫做KMP算法是因为这个算法是由三个人共同提出来的,就取三个人名字的首字母作为该算法的名字。其实KMP算法与BF算法的区别就在于KMP算法巧妙的消除了指针i的回溯问题,只需确定下次匹配j的位置即可,使得问题的复杂度由O(mn)下降到O(m+n)。
在KMP算法中,为了确定在匹配不成功时,下次匹配时j的位置,引入了next[]数组,next[j]的值表示P[0...j-1]中最长后缀的长度等于相同字符序列的前缀。
对于next[]数组的定义如下:
1) next[j]=-1 j=0
2) next[j]=max k:0<k<j P[0...k-1]=P[j-k,j-1]
3) next[j]=0 其他
如:
P a b a b a
j 0 1 2 3 4
next -1 0 0 1 2
即next[j]=k>0时,表示P[0...k-1]=P[j-k,j-1]
因此KMP算法的思想就是:在匹配过程称,若发生不匹配的情况,如果next[j]>=0,则目标串的指针i不变,将模式串的指针j移动到next[j]的位置继续进行匹配;若next[j]=-1,则将i右移1位,并将j置0,继续进行比较。
代码实现如下:

[cpp]view plaincopy
 
  1. intKMPMatch(char*s,char*p)
  2. {
  3. intnext[100];
  4. inti,j;
  5. i=0;
  6. j=0;
  7. getNext(p,next);
  8. while(i<strlen(s))
  9. {
  10. if(j==-1||s[i]==p[j])
  11. {
  12. i++;
  13. j++;
  14. }
  15. else
  16. {
  17. j=next[j];//消除了指针i的回溯
  18. }
  19. if(j==strlen(p))
  20. returni-strlen(p);
  21. }
  22. return-1;
  23. }

因此KMP算法的关键在于求算next[]数组的值,即求算模式串每个位置处的最长后缀与前缀相同的长度, 而求算next[]数组的值有两种思路,第一种思路是用递推的思想去求算,还有一种就是直接去求解。
1、按照递推的思想:
根据定义next[0]=-1,假设next[j]=k, 即P[0...k-1]==P[j-k,j-1]
1)若P[j]==P[k],则有P[0..k]==P[j-k,j],很显然,next[j+1]=next[j]+1=k+1;
2)若P[j]!=P[k],则可以把其看做模式匹配的问题,即匹配失败的时候,k值如何移动,显然k=next[k]。
因此可以这样去实现:

[cpp]view plaincopy
 
  1. voidgetNext(char*p,int*next)
  2. {
  3. intj,k;
  4. next[0]=-1;
  5. j=0;
  6. k=-1;
  7. while(j<strlen(p)-1)
  8. {
  9. if(k==-1||p[j]==p[k])//匹配的情况下,p[j]==p[k]
  10. {
  11. j++;
  12. k++;
  13. next[j]=k;
  14. }
  15. else//p[j]!=p[k]
  16. k=next[k];
  17. }
  18. }

2、直接求解方法

[cpp]view plaincopy
 
  1. voidgetNext(char*p,int*next)
  2. {
  3. inti,j,temp;
  4. for(i=0;i<strlen(p);++i)
  5. {
  6. if(i==0)
  7. {
  8. next[i]=-1;//next[0]=-1
  9. }
  10. elseif(i==1)
  11. {
  12. next[i]=0;//next[1]=0
  13. }
  14. else
  15. {
  16. temp=i-1;
  17. for(j=temp;j>0;--j)
  18. {
  19. if(equals(p,i,j))
  20. {
  21. next[i]=j;//找到最大的k值
  22. break;
  23. }
  24. }
  25. if(j==0)
  26. next[i]=0;
  27. }
  28. }
  29. }
  30. boolequals(char*p,inti,intj)//判断p[0...j-1]与p[i-j...i-1]是否相等
  31. {
  32. intk=0;
  33. ints=i-j;
  34. for(;k<=j-1&&s<=i-1;k++,s++)
  35. {
  36. if(p[k]!=p[s])
  37. returnfalse;
  38. }
  39. returntrue;
  40. }

http://poj.org/problem?id=2406

 

给定一个字符串,问最多是多少个相同子串不重叠连接构成。

KMP的next数组应用。这里主要是如何判断是否有这样的子串,和子串的个数。

若为abababa,显然除其本身外,没有子串满足条件。而分析其next数组,next[7] = 5,next[5] = 3,next[3] = 1,即str[2..7]可由ba子串连接构成,那怎么否定这样的情况呢?很简单,若该子串满足条件,则len%sublen必为0。sunlen可由上面的分析得到为len-next[len]。

因为子串是首尾相接,len/sublen即为substr的个数。

若L%(L-next[L])==0,n = L/(L-next[L]),else n = 1

[cpp]view plaincopy
 
  1. #include<iostream>
  2. #include<cstdio>
  3. usingnamespacestd;
  4. charpattern[1000002];
  5. intnext[1000002];
  6. /*
  7. kmp算法,需要首先求出模式串的next函数值
  8. next[j]=k,说明p0pk-1==pj-kpj-1,也就是说k为其前面相等串的长度
  9. */
  10. voidget_nextval(constchar*pattern)
  11. {
  12. inti=0,j=-1;
  13. next[0]=-1;
  14. while(pattern[i]!='\0')
  15. {
  16. if(j==-1||pattern[i]==pattern[j])//pattern[i]表示后缀的单个字符,pattern[j]表示前缀的单个字符
  17. {
  18. ++i;
  19. ++j;
  20. if(pattern[i]!=pattern[j])
  21. next[i]=j;
  22. else
  23. next[i]=next[j];
  24. }
  25. else
  26. j=next[j];//若j值不相同,则j值回溯
  27. }
  28. }//get_nextval
  29. intmain(void)
  30. {
  31. intlen;
  32. while(scanf("%s",pattern)!=EOF)
  33. {
  34. if(pattern[0]=='.')
  35. break;
  36. len=strlen(pattern);
  37. get_nextval(pattern);
  38. if(len%(len-next[len])==0)
  39. printf("%d\n",len/(len-next[len]));
  40. else
  41. printf("1\n");
  42. }
  43. return0;
  44. }

http://poj.org/problem?id=1961

大意:
定义字符串A,若A最多由n个相同字串s连接而成,则A=s^n,如"aaa" = "a"^3,"abab" = "ab"^2
"ababa" = "ababa"^1

给出一个字符串A,求该字符串的所有前缀中有多少个前缀SA= s^n(n>1)
输出符合条件的前缀长度及其对应的n

如aaa
前缀aa的长度为2,由2个'a'组成
前缀aaa的长度为3,由3个"a"组成

分析:KMP
若某一长度L的前缀符合上诉条件,则
1.next[L]!=0(next[L]=0时字串为原串,不符合条件)
2.L%(L-next[L])==0(此时字串的长度为L/next[L])

对于2:有str[0]....str[next[L]-1]=str[L-next[L]-1]...str[L-1]
=》str[L-next[L]-1] = str[L-next[L]-1+L-next[L]-1] = str[2*(L-next[L]-1)];
假设S = L-next[L]-1;则有str[0]=str[s]=str[2*s]=str[3*s]...str[k*s],对于所有i%s==0,均有s[i]=s[0]
同理,str[1]=str[s+1]=str[2*s+1]....
str[j]=str[s+j]=str[2*s+j]....
综上,若L%S==0,则可得L为str[0]...str[s-1]的相同字串组成,
总长度为L,其中字串长度SL = s-0+1=L-next[L],循环次数为L/SL
故对于所有大于1的前缀,只要其符合上述条件,即为答案之一

[cpp]view plaincopy
 
  1. #include<iostream>
  2. #include<cstdio>
  3. #include<cstring>
  4. usingnamespacestd;
  5. charpattern[1000002];
  6. intnext[1000002];
  7. /*
  8. kmp算法,需要首先求出模式串的next函数值
  9. next[j]=k,说明p0pk-1==pj-kpj-1,也就是说k为其前面相等串的长度
  10. */
  11. voidget_nextval(constchar*pattern)
  12. {
  13. inti=0,j=-1;
  14. next[0]=-1;
  15. while(pattern[i]!='\0')
  16. {
  17. if(j==-1||pattern[i]==pattern[j])
  18. {
  19. ++i;
  20. ++j;
  21. next[i]=j;
  22. }
  23. else
  24. j=next[j];
  25. }
  26. }//get_nextval
  27. intmain(void)
  28. {
  29. inti,len,n,j=1;
  30. while(scanf("%d",&n)!=EOF)
  31. {
  32. if(!n)
  33. break;
  34. scanf("%s",pattern);
  35. len=strlen(pattern);
  36. get_nextval(pattern);
  37. printf("Testcase#%d\n",j++);
  38. for(i=2;i<=len;i++)
  39. {
  40. if(i%(i-next[i])==0&&i/(i-next[i])>1)
  41. printf("%d%d\n",i,i/(i-next[i]));
  42. }
  43. printf("\n");
  44. }
  45. return0;
  46. }

http://poj.org/problem?id=2752
大意:
给出一个字符串A,求A有多少个前缀同时也是后缀,从小到大输出这些前缀的长度。

分析:KMP
对于长度为len的字符串,由next的定义知:
A[0]A[1]...A[next[len]-1]=A[len-next[len]]...A[len-1]此时A[0]A[1]...A[next[len]-1]为一个符合条件的前缀
有A[0]A[1]....A[next[next[len]]-1] = A[len-next[next[len] - next[next[len]]]...A[next[len]-1],故A[0]A[1]....A[next[next[len]]-1]也是一个符合条件的前缀
故从len=>next[len]=>next[next[len]] ....=>直到某个next[]为0均为合法答案,注意当首位单词相同时,也为答案。

[cpp]view plaincopy
 
  1. #include<iostream>
  2. #include<cstdio>
  3. #include<cstring>
  4. #include<vector>
  5. usingnamespacestd;
  6. charpattern[400002];
  7. intnext[400002];
  8. /*
  9. kmp算法,需要首先求出模式串的next函数值
  10. next[j]=k,说明p0pk-1==pj-kpj-1,也就是说k为其前面相等串的长度
  11. */
  12. voidget_nextval(constchar*pattern)
  13. {
  14. inti=0,j=-1;
  15. next[0]=-1;
  16. while(pattern[i]!='\0')
  17. {
  18. if(j==-1||pattern[i]==pattern[j])
  19. {
  20. ++i;
  21. ++j;
  22. next[i]=j;
  23. }
  24. else
  25. j=next[j];
  26. }
  27. }//get_nextval
  28. intmain(void)
  29. {
  30. inti,len,n;
  31. vector<int>ans;
  32. while(scanf("%s",pattern)!=EOF)
  33. {
  34. ans.clear();
  35. len=strlen(pattern);
  36. get_nextval(pattern);
  37. n=len;
  38. while(n)
  39. {
  40. ans.push_back(n);
  41. n=next[n];
  42. }
  43. if(pattern[0]==pattern[n-1])//首部、尾部字符相同
  44. ans.push_back(1);
  45. i=ans.size()-1;
  46. for(;i>0;i--)
  47. printf("%d",ans[i]);
  48. printf("%d\n",ans[0]);
  49. }
  50. return0;
  51. }
分享到:
评论

相关推荐

    KMP算法详解0.0.doc

    KMP算法详解0.0.doc,希望对在学数据结构与算法或对之感兴趣的人有所帮助!

    KMP算法:高效字符串匹配算法详解

    kmp算法内容概况: 本文将介绍一种名为KMP的字符串匹配算法。...KMP算法可以优化查找操作,提高应用程序的性能。 5. 字符串匹配问题解决者:在解决字符串匹配问题时,需要快速找到一个字符串在另一个字符串中的位

    2.KMP算法:高效字符串匹配算法详解

    kmp算法内容概况: 本文将介绍一种名为KMP的字符串匹配算法。...KMP算法可以优化查找操作,提高应用程序的性能。 5. 字符串匹配问题解决者:在解决字符串匹配问题时,需要快速找到一个字符串在另一个字符串中的位

    kmp算法概述、原理及应用详解.pdf

    KMP算法是一种高效的字符串匹配算法,由D.E.Knuth、J.H.Morris和V.R.Pratt共同提出,因此人们称它为克努特—莫里斯—普拉特操作(简称KMP算法)。这种算法主要用于解决字符串匹配问题,即从主字符串(text)中搜索一...

    KMP 算法实例详解

    主要介绍了KMP 算法实例详解的相关资料,MP的关键是求出next的值、先预处理出next的值,需要的朋友可以参考下

    深入串的模式匹配算法(普通算法和KMP算法)的详解

    本篇文章是对串的模式匹配算法(普通算法和KMP算法)的应用进行了详细的分析介绍,需要的朋友参考下

    数据结构算法

    树状数组 经典算法题每日演练——第九题 优先队列 经典算法题每日演练——第八题 AC自动机 经典算法题每日演练——第七题 KMP算法 经典算法题每日演练——第六题 协同推荐SlopeOne 算法 经典算法题每日演练——第五...

    java数据结构与算法.pdf

    包含了各种数据结构和算法(java)的实现方式和详解(图解),包括单双链表、环形链表(约瑟夫问题)、栈、后缀表达式、中缀表达式转后缀表达式、迷宫问题、八大排序算法、多种查找算法、哈希表、二叉树实现以及操作...

    考研-数据结构-殷人昆.zip

    1.1.1 考研综合应用题中算法设计部分的代码书写规范1 1.1.2 考研中的C 与C 语言基础3 1.2 算法的时间复杂度与空间复杂度分析基础 12 1.2.1 考研中的算法时间复杂度分析 12 1.2.2 例题选讲 12 1.2.3 考研中的算法空间...

Global site tag (gtag.js) - Google Analytics