KMP算法

  KMP 算法相对于 Brute-Force 算法有比较大的改进,主要是消除了主串指针的回溯,从而使算法效率有了某种程度的提高。

暴力BF匹配

int BF(char S[], char T[])
{
	int i = 0, j = 0;
	while(S[i] != '\0' && T[j] != '\0'){
		if(S[i] == T[j]){
			i++; j++;
		}
		else {
			i = i-j+1;//主串指针的回溯
			j = 0;
		}
	}
	if(T[j] == '\0') return (i-j); //主串中存在该模式返回下标号 
	else return -1; //主串中不存在该模式 
}

提取加速匹配信息

  KMP算法主要是通过消除主串指针的回溯来提高匹配的效率的,实际上是提取并运用了加速匹配的信息!

  对于模式串 t 的每个元素 t j,都存在一个实数 k ,使得模式串 t 开头的 k 个字符(t0, t1,…,tk-1)依次与 tj 前面的 k 个字符(tj-k,tj-k+1,…,tj-1 这里第一个字符 tj-k 最多从 t1 开始,所以 k < j)相同。如果这样的 k 有多个,则取最大的一个。采用 next 数组表示,即 next[j] = MAX{k}。

void Getnext(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];//递归寻找更短的公共前后缀
   }
}

next 求解过程

1. 当 j = 0 或 1 的情况

  当 j 的值为 0 或 1 的时候,它们的 k 值都为 0,即 next[0] =0, next[1] =0。但是为了后面 k 值计算的方便,我们将 next[0] 的值设置成 -1。

2.当 t[j] == t[k] 的情况

在这里插入图片描述
  当 t[j] == t[k] 时,必然有"t[0]…t[k-1]" == “t[j-k]…t[j-1]”,此时的 k 即是相同子串的长度,即next[j] = k。因为有"t[0]…t[k-1]" == “t[j-k]…t[j-1]” 且 t[j] == t[k],则有"t[0]…t[k]" == " t[j-k]…t[j]",这样也就得出了next[j+1] = k+1。

3. 当t[j] != t[k] 的情况

在这里插入图片描述

最长公共前后缀

   字符串的前缀是指不包含最后一个字符的所有以第一个字符开头的连续子串;字符串的后缀是指不包含第一个字符的所有以最后一个字符结尾的连续子串。
  例如对于字符串 abacaba,其前缀有 a, ab, aba, abac, abacab,后缀有bacaba, acaba, caba, aba, ba, a。最长公共前后缀就是 aba。
  字符串"P0…PkPk+1…Pj"中子串"P0…Pk"的最长公共前后缀长度用next数组表示为 next[k+1]。

在这里插入图片描述
递归寻找最长公共前后缀:在 Pk != Pj 时,k = next[k],用 Pnext[k] 去跟 Pj 继续匹配。

  在 Pj 之前,有一段长度为 next[ j ] = k 的已匹配串,即"P0…Pk-1" = “Pj-k…Pj-1”,则子串"P0…Pk-1"为串"P0…Pj-1"的最长公共前后缀。
  在 Pk 之前,有一段长度为 k’ = next[k] 的已匹配串,即"P0…Pk’-1" =“Pk-k’…Pk-1”(蓝色部分),则子串"P0…Pk’-1"为"P0…Pk-1"的最长公共前后缀。
  由以上两步,则"P0…Pk’-1"=“Pj-k’…Pj-1”,如果 Pnext[k] 能和 Pj 匹配 ,那么寻找到"P0…Pj"的最长公共前后缀"P0…Pnext[k]";如果还是不匹配,下一步 Pnext[next[k]…] 去跟Pj继续匹配,直到找到长度更短公共前后缀。

KMP 算法实现

在这里插入图片描述

当 “Si-j…Si-1” == “T0 … Tj-1”,但 Si != Tj ,而"T0…Tk" == “Tj-k…Tj-1”,则next[j] = k。

在这里插入图片描述

由上 可知,“T0…Tk” == “Si-k…Si-1”。

在这里插入图片描述

将模式串T右移j-k个字符,继续匹配Si与Tk,而不是模式串T只右移1个字符,且避免目标串S的指针回溯从头和模式串T进行匹配。

int KMP(String s, String t)
{
   int next[MaxSize], i = 0, j = 0;
   Getnext(t, next);//得到next[]数组
   while(i < s.length && j < t.length)
   {
      if(j == -1 || s[i] == t[j])
      {
         i++;
         j++;
      }
      else j = next[j];    //j回退,更短公共前后缀
   }
   if(j >= t.length)
       return (i-t.length);  //匹配成功,返回子串的位置
   else
      return (-1);       //没找到
}

改进后 next 求解方法

在这里插入图片描述

目标串S=“ABACDA”;
模式串T=“ABAB” 且 next数组应该是[ -1,0,0,1 ](可由最长公共前后缀方法求);
当 Si != Tj 时,且next[j] = 1,将 j 移动到第1个元素,进行匹配。

在这里插入图片描述

这一步是完全没有意义的,因为后面的B已经不匹配了,那前面的B也一定是不匹配的,同样的情况其实还发生在第 2 个元素A上。显然,发生问题的原因在于t[j] == t[next[j]]。

void Getnext(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];
   }
}

  1. KMP算法详解.
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值