ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

KMP算法的详细阐述

2019-03-31 16:37:55  阅读:327  来源: 互联网

标签:阐述 匹配 overfront 后缀 value next ++ 算法 KMP


KMP详细解释

basic algorithm


 

前言

自己在做leetcode的时候,遇到了一道字符串匹配的问题,开始的时候用的是c++中的自带库以及定制操作find,而后在讨论区看到了KMP算法,遂去了解,但是没想到这个算法对我来说,如此晦涩难懂,今天终于算是有了个一知半解,所以将其写下来。

 

KMP的出现

在我们平常做这种字符匹配的题目时,大部分人第一想法都是使用暴力破解

 
  1. 对于s主串 ababababca 有一个 匹配串abababca 需要去匹配自身在其中的位置
  2. 我们大部分的想法,就是一个i指针指向s主串,一个j指针指向匹配串。
  3. 然后i所指的与匹配串j所指的一一进行匹配,如果遇到不相同的就将i+1,j归零,再次匹配
  4. if(s[i]==p[j]){
  5. i++;
  6. j++;
  7. }
  8. else{
  9. i=i-j+1;
  10. j=0;
  11. }
  • 但是发现了吗,我们在重复的匹配之前已经匹配过的字段,重复的去做一些已经做过的工作,
    如 S串:ababababca与P:abababca的暴力破解中,第一次执行到i=6,j=6的时候发生了不匹配,这时候就将再将i调整到第二位,然后j归零,再次进行比较,但是我们之前所匹配成功的字段ababab就这样浪费了,没有用到他,而kpm的核心就是用到了这个已匹配字段的信息。
    所以,kmp是通过利用已经匹配字段的信息,来加快字符匹配,以及减少重复工作。时间复杂度o(n+m);
 

KMP的核心思想

我在看博客的时候,一直不明白的其实就是next数组的求解,其实next数组就是一个前缀后缀的最大集合的一个简化版,因为next确实不等于前缀后缀的最大集合,但其实就是一个对于最大前缀后缀的后移。
这篇文章讲的十分清楚https://www.zhihu.com/question/21923021/answer/281346746
意思就是,对于abababca中

abababca

其中

 
  1. 对于a来说,无前后缀,则value值为0
  2. 对于ab,无前后缀相等,则value为0
  3. 对于aba,有相同前后缀,{a},长度为1,则value为1
  4. 对于abab ,有相同前后缀,最长{ab},长度为2,则value为2
  5. 对于ababa,有相同前后缀,最长为{aba},长度为3,则value为3
  6. 对于ababab,有相同前后缀,最长为{abab},长度为4,则value为4
  7. 对于abababc,无相同前后缀,value为0
  8. 对于abababca
s indexabababca
index 0 1 2 3 4 5 6 7
value 0 0 1 2 3 4 0 0

而以上的只是相同前后缀的值,称作PMT,而我们将PMT放在匹配字段中看一下
就像我们开始那样,对于

主串ababababca
匹配串abababca

当i=6,j=6时,两个字符不匹配,一个为a一个为c,因为在之前我们就发现,匹配字段中(0~j-1)与主串中(i-j,i-1)的位置是匹配的,所以我们去找寻这个一个字段相同前后缀的值,这个字段是
|a|b|a|b|a|b
|:|
而我们找到这个字段 他的value值就是4,意思就是说,他的前后缀有4个位置是相匹配的,所以我们就可以在此基础上将匹配串向右移动两个位置(移动位置=此时j指针所指的位置-value值)PS:匹配串移动两个位置其实就是j指针向左移动两个位置,然后izhi'zh
,则我们就可以跳过abab的匹配,因为它的前后缀是相同,肯定是已经配上了,所以节省了很大的工作。
就是使用相同前后缀的值所带来的效果。
那么既然每次不匹配的时候,我们都要从当前不匹配字符向前移动一个字符,去寻找他的相同前后缀值,那么为何不节省一点,直接将整个value数组向后移动一个位置

s indexabababca
index 0 1 2 3 4 5 6 7
value-next -1 0 0 1 2 3 4 0

其实这就是value数组的来历,就是将PMT向后移,更好的适配我们的程序
那么这个时候我们的代码就可以读懂了

 
  1. void MakeNext(string p ,vector<int> next){
  2. next.reserve(p.length());
  3. next[0]=-1;//产生后缀
  4. int overfront=-1;//指向匹配字段的头部以前,其实就是为了构建出一个后缀。
  5. int i=0;
  6. while(overfront<p.length()-1){//开始匹配字段的自我比较,找寻相同前缀后缀码
  7. if(overfront==-1||p[overfront]==p[i]){
  8. ++overfront;
  9. ++i;
  10. next[i]=overfront;
  11. }
  12. else{
  13. overfront=next[overfront];//overfront回溯,以求在i之前找到匹配字段,如果没有最后会回溯到overfront为-1,然后再进入上一步,将其value值设定为0
  14. }
  15. }
  16. }

以下是进行一个伪代码运行

 
  1. 第一次 overfront(后面简称of)=-1,i=0;
  2. ++of;
  3. ++i;
  4. next[i]=of;

此时next数组

next-index01
next-value -1 0

此时i=1,of=0;

 
  1. 进行判断of=0,且p[of]!=p[i];
  2. of=next[of];
  3. of=-1;
  4. of=-1;i=1;
  5. ++overfront;
  6. ++i;
  7. next[i]=overfront;

此时的next数组

next-index012
next-value -1 0 0

of=0,i=2;

 
  1. because p[of]==p[i]
  2. ++of;
  3. ++i;
  4. of=1,i=3
  5. next[i]=of

此时的next数组

next-index0123
next-value -1 0 0 1

如此依次往下
最后得出

s indexabababca
index 0 1 2 3 4 5 6 7
value-next -1 0 0 1 2 3 4 0

那么在完成了next数组求值之后,我们就要开始进行字符串的匹配,就跟我们开始讲的那样,一步步的匹配,然后遇到不匹配的回溯。

代码如下

 
  1. int malepular(string a,string b){
  2. int i=0;
  3. int j=0;//两个指针分别指向a,b
  4. int pren=a.length();
  5. int tpre=a.length();
  6. while(i<pren&&j<tpre){
  7. if(j==-1||a[i]==b[j]){++i;++j;}
  8. else{
  9. j=next[j];//
  10. }
  11. }
  12. if(j==tpre)return i-j;
  13. else
  14. retun -1;
  15. }
 

参考文献

http://wiki.jikexueyuan.com/project/kmp-algorithm/define.html
https://www.zhihu.com/question/21923021/answer/281346746
https://www.bilibili.com/video/av3246487?from=search&seid=15447356355389591502

标签:阐述,匹配,overfront,后缀,value,next,++,算法,KMP
来源: https://www.cnblogs.com/Yekko/p/10631876.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有