KMP算法

问题描述

给定一个模式串 t = t 0 t 1 ⋯ t n t=t_0t_1 \cdots t_n t=t0t1tn,目标串 s = s 0 s 1 ⋯ s m s=s_0s_1 \cdots s_m s=s0s1sm,要求找出模式串在目标串的出现位置(字符串匹配)。

解决方案

暴力美学

for(int i = 0; i < m - n; i++){
	bool flag = true;
	for(int j = 0; j < n; j++){
		if(s[i+j] != t[j]) flag = false;
	}
	if(flag) break;
}

KMP

KMP算法是一种改进的字符串匹配算法,由D.E.KnuthJ.H.MorrisV.R.Pratt提出的。
下图展示了字符串匹配的过程:
字符串匹配的过程按照暴力的算法,当模式串和目标串发生失配时,模式串会从头开始进行匹配,事实上,这样可能存在冗余的步骤。假设模式串满足 t 0 t 1 ⋯ t k − 1 = t j − k t j − k + 1 ⋯ t j − 1 t_0t_1\cdots t_{k-1}=t_{j-k}t_{j-k+1}\cdots t_{j-1} t0t1tk1=tjktjk+1tj1,根据 t 0 t 1 ⋯ t j − 1 = s i − j s i − j + 1 ⋯ s i − 1 t_0t_1\cdots t_{j-1}=s_{i-j}s_{i-j+1}\cdots s_{i-1} t0t1tj1=sijsij+1si1,因此有以下关系 t 0 t 1 ⋯ t k − 1 = s i − k s i − k + 1 ⋯ s i − 1 t_0t_1\cdots t_{k-1}=s_{i-k}s_{i-k+1}\cdots s_{i-1} t0t1tk1=siksik+1si1,那么此时模式串就没有必要从头开始,只需回退到 k k k的位置即可。
在这里插入图片描述
通过观察发现回退的位置只和模式串相关,与目标串无关。因此求得模式串的每一个元素的最终回退位置,将会加快求解过程。

next数组求解

n e x t next next数组记录的应当是模式串中每个元素发生失配时最终回退位置,同时 n e x t [ j ] next[j] next[j]满足 t 0 t 1 ⋯ t k − 1 = t j − k t j − k + 1 ⋯ t j − 1 t_0t_1\cdots t_{k-1}=t_{j-k}t_{j-k+1}\cdots t_{j-1} t0t1tk1=tjktjk+1tj1
假设 n e x t [ j ] = k next[j] = k next[j]=k, 现在我们要求 n e x t [ j + 1 ] next[j+1] next[j+1]:

  • t [ j ] = = t [ k ] t[j] == t[k] t[j]==t[k]时,由于存在 n e x t [ j ] = k next[j] = k next[j]=k t 0 t 1 ⋯ t k − 1 = t j − k t j − k + 1 ⋯ t j − 1 t_0t_1\cdots t_{k-1}=t_{j-k}t_{j-k+1}\cdots t_{j-1} t0t1tk1=tjktjk+1tj1,可以得到 t 0 t 1 ⋯ t k = t j − k t j − k + 1 ⋯ t j t_0t_1\cdots t_{k}=t_{j-k}t_{j-k+1}\cdots t_{j} t0t1tk=tjktjk+1tj n e x t [ j + 1 ] = k + 1 next[j+1] = k+1 next[j+1]=k+1
    在这里插入图片描述
  • t [ j ] ! = t [ k ] t[j] != t[k] t[j]!=t[k]时,求解 n e x t [ j + 1 ] next[j+1] next[j+1],需要找满足 t 0 t 1 ⋯ t k ′ − 1 = t j − k ′ + 1 t j − k ′ + 2 ⋯ t j t_0t_1\cdots t_{k'-1}=t_{j-k'+1}t_{j-k'+2}\cdots t_{j} t0t1tk1=tjk+1tjk+2tj k ′ k' k。由于 t 0 t 1 ⋯ t k − 1 = t j − k t j − k + 1 ⋯ t j − 1 t_0t_1\cdots t_{k-1}=t_{j-k}t_{j-k+1}\cdots t_{j-1} t0t1tk1=tjktjk+1tj1 t [ j ] ! = t [ k ] t[j] != t[k] t[j]!=t[k],那么求解满足 t 0 t 1 ⋯ t k ′ − 1 = t j − k ′ + 1 t j − k ′ + 2 ⋯ t j t_0t_1\cdots t_{k'-1}=t_{j-k'+1}t_{j-k'+2}\cdots t_{j} t0t1tk1=tjk+1tjk+2tj k ′ k' k就等于模式串 t 0 t 1 ⋯ t k t_0t_1\cdots t_{k} t0t1tk和目标串 ⋯ t j − k t j − k + 1 ⋯ t j \cdots t_{j-k}t_{j-k+1}\cdots t_{j} tjktjk+1tj j j j这个位置发生失配,模式串需要回退的最终位置 k ′ ′ k'' k加上1即 k ′ = k ′ ′ + 1 = n e x t [ k ] + 1 k'=k''+1=next[k]+1 k=k+1=next[k]+1。(事实上,可能不会一次回退成功,需要不断比较 t [ j ] = = t [ k ′ ′ ] t[j] == t[k''] t[j]==t[k], 如果存在满足等于,其实就回到前面第一点分析的内容;如果一直不等,就会到达边界。)
    在这里插入图片描述

KMP 代码

求解 n e x t next next数组

void getNext(vector<int> &next, string t)
{
   int j = 0,k = -1;
   next[0] = -1;
   while(j < t.length()-1)
   {
      if(k == -1 || t[j] == t[k])
      {
         j++; k++;
         next[j] = k;
      }
      else k = next[k];
   }
}

KMP

int KMP(string s, string t)
{
   vector<int> next(t.length());
   int i = 0, j = 0;
   getNext(next, t);
   while(i < s.length() && j < t.length())
   {
      if(j == -1 || s[i] == t[j])
      {
         i++;
         j++;
      }
      else j = next[j];
   }
   if( j >= t.length())
       return (i - t.length());
   else
      return (-1);
}

优化求解next数组

前面说过, n e x t next next数组存储的是模式串中每一个元素的最终回退位置。

void getNext(vector<int> &next, string t)
{
   int j = 0,k = -1;
   next[0] = -1;
   while(j < t.length()-1)
   {
      if(k == -1 || t[j] == t[k])
      {
         j++; k++;
         if(t[j] == t[k]) next[j] = next[k];
         else next[j] = k;
      }
      else k = next[k];
   }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值