KMP算法的详细阐述

KMP详细解释

basic algorithm


 

前言

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

 

KMP的出现

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

 
  1. 对于s主串 ababababca 有一个 匹配串abababca 需要去匹配自身在其中的位置
  2. 我们大部分的想法,就是一个i指针指向s主串,一个j指针指向匹配串。
  3. 然后i所指的与匹配串j所指的一一进行匹配,如果遇到不相同的就将i+1j归零,再次匹配
  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,无前后缀相等,则value0
  3. 对于aba,有相同前后缀,{a},长度为1,则value1
  4. 对于abab ,有相同前后缀,最长{ab},长度为2,则value2
  5. 对于ababa,有相同前后缀,最长为{aba},长度为3,则value3
  6. 对于ababab,有相同前后缀,最长为{abab},长度为4,则value4
  7. 对于abababc,无相同前后缀,value0
  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)=-1i=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

posted @ 2019-03-31 16:30  Yekko  阅读(360)  评论(0编辑  收藏  举报