KMP算法

BF算法到匹配不成功的时候子串需要回溯,这样就会提高时间复杂度,KMP算法是每当一趟匹配过程中出现字符比较不相等时,不要回溯i指针,而是利用已经得到的“部分匹配”的结果将模式向右“滑动”尽可能远的一段距离后,继续进行比较。

以一般情况进行说明,假设主串为"S1, S2, ...,Sn",模式串为:"T1, T2, ...Tm",当主串中的第i个字符与模式中的第j个字符比较不相等时,主串的第i个字符应该与模式中的哪个字符再比较?

假设此时应该与模式串的第k(k<j)个字符继续比较,则模式中前k-1个字符的子串必须满足下面这个关系式,且不可能存在k1>k满足该关系式  (因为k<j,若k1>k,则K1字符在j之后,j还没完成无法进行下一个)

满足公式:"T1T2...Tk-1 = Si-k+1Si-k...Si-1"(Si-k+1Si-k...Si-1表示主串的i字符前有k-1个字符的子串)

因为模式串是从1可以和主串匹配到j-1,所有下列该公式:

 Tj-k+1Tj-k+2...Tj-1 = Si-k+1Si-k...Si-1(Tj-k+1Tj-k+2...Tj-1是模式串第i个元素前k-1个元素,因为模式串是在第j个元素和主串的第i匹配不成功,则,前面的元素是匹配成功的)

故可以推得:"T1T2...Tk-1  =  Tj-k+1Tj-k+2...Tj-1"

因此当主串的第i个元素与模式串的第j个元素匹配失败时,就可以将模式向右滑动至模式串中的第k个元素与主串中的第i个元素对齐就成。

因此令next[j] = k(类似于一个函数),则next[j]表示当模式串中第j个字符与主串的第i个字符不匹配时,在模式中需要和主串该字符进行比较的字符的位置。可以定义一个next的函数的定义

K = next[j] = 

  1.    0   j = 1(T1与Si比较不等时,下一步进行T1与Si+1比较)
  2.    Max  (K| 1<k<j,且有"T1T2...Tk-1  =  Tj-k+1Tj-k+2...Tj-1",K = k-1  +  1)
  3.    1    k = 1 (不存在相同的子串,下一步进行T1与Si比较)

若在匹配过程中Si = Ti匹配成功,则i和j分别增加1

若未匹配成功,j = next[j],往回退有两种可能:

  1. 退到某个j时匹配成功,T(next[next[..next[j]..]] == Si,则指针都加1,再继续进行比较
  2. j退到值为0,则继续将模式串往右继续滑动一个位置(就相当于匹配不成功的时候也应将模式串next[j]移到和主串的第i个字符对齐,若最后一个还是不匹配,故还需要将其右滑动一个位置然后使模式串的第1个元素与主串的第i+1个元素对齐
int Index_KMP(SString S, SString T, int pos)
{
  int i,j,pos;//pos是用来记录模式串是在主串的哪一个位置开始完全匹配成功,
  i = 1;
  j = 1;
  while(i < S.length && j < T.length)//两个串均未到串尾
  {
    if(j == 0 || S.ch[i] == T.ch[j])//规定j == 0是因为next[1] = 0;这时就是未匹配成功的第2个可 
                                      能;S.ch[i] == T.ch[j]匹配成功,都要加1;
    {
      ++i;//主串向右移动
      ++j;//模式串向右移动
    }
    else
        j = next[j];
  }
  if(j > T.length)
     return pos = i - T.length;//匹配成功,返回pos值
  else
     return 0;//匹配失败,因为在规定的时候串的第一不放有效值,故返回0的时候为匹配失败

}

这是在知道next[]的值,那么next[]应该如何求?

首先next[1] = 0;

设next[j] = k,则满足:"T1T2...Tk-1  =  Tj-k+1Tj-k+2...Tj-1",那么next[j+1] = ?

(1)若Tk = Tj,则表明模式串中:"T1T2...Tk-1Tk =  Tj-k+1Tj-k+2...Tj-1Tj",所以next[j+1] = k+1;

(2)若 Tk != Tj,则继续比较Tnext[k]与Tj,知道匹配成功,那此时next[j+1] = next[此时该字符与T匹配成功的位置]或者到不存在模式串的字符与Tj匹配成功,即next[j] = next[1]+1 = 1

void get_next(SString T, int next())
{
  int i = 1;
  next[1] = 0;
  int j = 0;
  while(i<T.length)
  {
    if(j == 0 || T.ch[i] == T.ch[j])
    {
      ++i;
      ++j;
      next[i] = j;
    else
        j = next[j];
    }
  } 
 
}
//因为j == 0;所以i= 2; j = 1; 则next[2] = 1;然后该求next[3],由前面的可知若if()中满足条件则next[3] = next[2]+1,这就相当于一种递归

前面说的都是从大往下来说明,而该代码看起来是从小往大,但是当要求next[3]的时候,仍然是以next[2]来运算,如果比较之后不相等,然后执行j = next[j],和前面说如何求next[j+1]的(2)是相同的,如果if条件达成,就会++j,就和前面(2)中一样next[j] = next[1]+1 = 1

求nextval[];

//事先求出next[]
nextval[1] = 0;
for(int j = 2; j < T.length; ++j)
{
  if(T.ch[j] == T.ch[next[j]])
    {
      nextval[j] = nextval[next[j]];
    }
   else
      nextval[j] = next[j];
}

求next[]快速方法:

 例题:

串"ababaaababaa"的next数组为(C)

A,012345678999     B,012121111212        C,011234223456   D,0123012322345

第一个肯定是0;第二个仅有一个前面仅有一个字符,所以为1;第三个前面的前缀为a,后缀为b,所以next[3] = 0+1 = 1;... 第六个相等的最长的前后缀为:aba,所以next[6] = 3+1 = 4

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,下面我来为您讲解使用KMP算法解决字符串匹配问题的方法。 KMP算法是一种比较高效的字符串匹配算法,它的核心思想是利用已经匹配过的信息,尽可能减少不必要的字符比较次数,从而提高匹配效率。具体实现时,KMP算法利用前缀和后缀的概念,对于每个字符,计算出它的最长前缀和最长后缀的匹配长度,然后根据这些信息来移动模式串,从而减少比较次数。 下面是使用KMP算法解决字符串匹配问题的代码实现: ```python def str_str(s, n): if not n: return 0 if not s: return -1 m, k = len(s), len(n) next = get_next(n) i = j = 0 while i < m and j < k: if j == -1 or s[i] == n[j]: i += 1 j += 1 else: j = next[j] if j == k: return i - k else: return -1 def get_next(n): k, j = -1, 0 next = [-1] * len(n) while j < len(n) - 1: if k == -1 or n[k] == n[j]: k += 1 j += 1 next[j] = k else: k = next[k] return next ``` 需要注意的是,KMP算法中的next数组表示的是模式串中每个位置的最长前缀和最长后缀的匹配长度,而不是暴力匹配算法中的每个位置的最长前缀和最长后缀。因此,KMP算法中的next数组需要先计算出来,然后再根据它来移动模式串。 接下来,我来给您提供一组测试用例,证明KMP算法的正确性: ```python assert str_str("hello", "ll") == 2 assert str_str("aaaaa", "bba") == -1 assert str_str("mississippi", "issip") == 4 ``` 这些测试用例可以验证KMP算法的正确性。相比暴力匹配算法KMP算法的时间复杂度为O(m+n),可以在较短的时间内解决字符串匹配问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值