弄透KMP算法,这篇就够了

本文详细解释了KMP算法中的前缀表概念,展示了其在构建过程中如何利用动态规划思想,以及如何通过前缀表指导字符串匹配,从而提高搜索效率。KMP算法的时间复杂度为O(n+m)相较于暴力解法的O(n*m)有显著优势。
摘要由CSDN通过智能技术生成

KMP的思想就是当字符串发生不匹配时,根据已经知道的一部分匹配内容来避免重头匹配

前缀是什么?

前缀是从首字母开始到任意位置i结束的特殊子串。真前缀不包含尾字母

例:aabdfdf的真前缀

a

aa

aab 

aabd

aabdf

aabdfd

什么是后缀?

后缀是从某一位置i开始到字符串结尾的特殊子串。真后缀不包含首字母

例:aabdfdf的真后缀

f

df

fdf

dfdf

bdfdf

abdfdf

什么是前缀表?

前缀表记录了当前位置i(包括i)之前,字符串中相等的真前后缀长度

前缀表用来记录当前位置,文本和模式串不匹配时,模式串应该从哪里重新匹配

aabaaf模式串
010120前缀表

让我们来看看前缀表怎么得到

前缀表记录了当前位置i(包括i)之前,字符串中相等的(真)前后缀最大长度

i=0时

长度为1的a,真前后缀长度为0,相等的真前后缀长度为0

i=1时

长度为2的aa,真前缀为a,真后缀为a,相等的真前后缀长度为1

i=2时

长度为3的aab,真前缀a,aa,真后缀为b,ab,无相等的真前后缀,相等的真前后缀长度为0

i=3时

长度为4的aaba,真前缀a,aa,aab.真后缀为a,ba,aba,相等的真前后缀为a,相等的真前后缀长度为1

i=4时

长度为5的aabaa真前缀为a,aa,aab,aaba真后缀为a,aa,baa,abaa,相等的真前后缀为aa,a相等的真前后缀最大长度为2

i=5时

长度为6的aabaaf,真前缀为a,aa,aab,aaba,aabaa真后缀为f,af,aaf,baaf,abaaf无相等的真前后缀,相等的真前后缀长度为0

 前缀表有什么用?

很多教材都告诉我们要建立前缀表,但是前缀表到底起什么作用我们确不得而知,导致看代码总迷迷糊糊的.

前缀表能告诉我们原串 haystack[i]与模式串needle[j]不匹配时,j应该移动到哪个位置后,继续比较 haystack[i]与needle[j],所以前缀表具有告诉我们当前位置匹配失败,跳到之前已经匹配过的地方的能力

如何写代码得到前缀表?

定义两个指针i和j

j指向真前缀末尾位置(同时j+1是最长相等真前后缀的长度)

i指向真后缀末尾位置。

初始化:j从0开始遍历模式串,i从1开始(因为真后缀不包括首字符)

这里是动态规划的思想

真前缀末尾位置的字符==i指向真后缀末尾位置的字符,最长相等前一最长真前后缀的长度加1

真前缀末尾位置的字符!=i指向真后缀末尾位置的字符,根据KMP的思想,我们可以回退到可能匹配的位置,j = next[j-1]如果匹配成功 ,长度等于j+1,匹配不成功继续回退

边界条件,当回退到j=0时并且s[i]和s[j]不相等,next[i]=j.(j=0,此时最长相等真前后缀长度为0)

代码如下:

你可以理解为这是在边使用next数组边建立next数组的过程

图解:

void getNext(int* next, const string& s){
  int j = 0;//初始化,next数组起始位置为0
  next[0] = j;
  for(int i = 1;i < s.size();i++){
   while (j > 0 && s[i] != s[j]){
     j = next[j-1];//回退到可能匹配的位置
   }
   if(s[i] == s[j]){
   j++;//如果匹配,最长相等真前后缀长度加1
   }
   next[i] = j;//j ==0 && s[i]==s[j] 的情况在这里体现
  }
}

如何使用前缀表?

                             模式串 

模式串aabaaf
下标012345

                                                                                           i

文本aabaabaafa
模式串aabaaf
next010120

                                                                                            j

b和f 不匹配了,f的前一个坐标对应的next[j-1]是j需要回退到的位置,j要退回到模式串索引为2的位置

                                                                                           i

文本aabaabaafa
模式串aabaaf
next010120

                                                                                           j

然后从i==5和j==2的位置开始比较

假设n为文本串长度,m为模式串长度,因为在匹配的过程中,根据前缀表不断调整匹配的位置,可以看出匹配的过程是O(n),之前还要单独生成next数组,时间复杂度是O(m)。所以整个KMP算法的时间复杂度是O(n+m)的。

暴力的解法显而易见是O(n × m),所以KMP在字符串匹配中极大地提高了搜索的效率。

KMP算法如何实现匹配过程?

int kmp(string l,string needle){
   if(needle.size( ) == 0){
     reutrn 0;//模式串长度为0
   }
   int next[needle.size( )];//建立next数组
   getNext(next, needle);
   int j = 0;
   for(int i = 0; i < l.size( ); i++){//开始匹配
   while (j > 0 && l[i] != needle[j]){//匹配不成功回退
         j = next[j-1];
   }
   if( l[i] == needle[j]){
     j++;//匹配成功就匹配下一个字符
   }
   if (j == needle.size){//匹配完了就返回首字母的位置
   return (i-needle.size( )+1);
   }
   return -1;
}

注:有些程序员喜欢讲next数组集体减1后使用,这仅仅是KMP算法实现上的问题,思想都是一样的

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值