KMP字符串匹配算法

KMP字符串匹配算法

  • 以往字符串匹配我都是直接暴力匹配(时间复杂度O(mn)),最近学习了KMP算法,只想说KMP杀我
  • KMP是出了名的难懂,各种博客上的算法思想都大同小异,只是实现代码有些不同
  • KMP算法的关键是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的
  • 具体实现的关键是找到next[]数组,数组本身包含了模式串的局部匹配信息
  • 时间复杂度O(m+n)
  • 先不管next[]数组的产生,讲讲KMP算法是如何实现匹配的

文本串为:a b c a b c a b a b c a b a a

模式串为:a b c a b a a

1、从下标0的位置开始依次匹配,直到c和a不匹配了,i,j停下
在这里插入图片描述
2、此时 j 回退到 next[ j ] 值“2”的地方,即 c 继续开始 i++,j++匹配直到b和a又不匹配了停下
在这里插入图片描述
3、此时 j 回退到 next[j] 的值“1”的地方,即 b 继续开始 i++,j++匹配
在这里插入图片描述
和暴力匹配最不同的两点:

  • j 每次回退不一定到模式串的最开头,而是到next [ j ]值的地方
  • 一遇到不匹配的字符,i 不会回退到一开始匹配字符的后一个字符,而是停住等待j回退后继续比较

以下是KMP代码:

int KmpSearch(char* s, char* p)  
{  
    int i = 0;  
    int j = 0;  
    int sLen = strlen(s);  //文本长度
    int pLen = strlen(p);  //模式长度
    while (i < sLen && j < pLen)  
    {  
        //①如果j = -1,或者当前字符匹配成功(即S[i] == P[j]),都令i++,j++      
        if (j == -1 || s[i] == p[j])  
        {  
            i++;  
            j++;  
        }  
        else  
        {  
            //②如果j != -1,且当前字符匹配失败(即S[i] != P[j]),则令 i 不变,j = next[j]      
            //next[j]即为j所对应的next值        
            j = next[j];  
        }  
    }  
    if (j == pLen)  
        return i - j;  //匹配成功返回文本匹配成功的第一个字符下标
    else  
        return -1;  //匹配失败返回-1
}  
接下来再讲讲 next[ ] 数组的作用及意义
  • 首先要明确:

    • next[] 数组是帮助 j 回退到合适的位置的(既要避免漏判又要避免无意义的比较)

    • next[]数组是模式串与自己匹配得到的

  • 先解释为什么 next[] 数组第一个值定为 -1:

    • 如果文本某个字符(i=x)和模式第一个字符(j=0)不匹配,则 j 回退到 next [ j ] 值“-1”处(虽然没有实际意义),但能进入if 判断使 i++,j++,即比较文本 i=x+1下标的字符与模式 j=0 的字符
    • 有人会问:那为什么不直接把next[0]定为0呢?——那样就一直循环进入else里 j 回退到0,无法比较下去

在这里插入图片描述

  • 再解释next[] 数组剩余的值表示什么意思(也是手动求解方法):

    • 首先是next[1]=0,表示索引1之前的子串"a"没有公共子串
    • next[2]=0,表示索引2之前的子串"ab"也没有公共子串
    • next[3]=0,表示索引3之前的子串"abc"没有公共子串
    • next[4]=1,表示索引4之前的子串"abca"其中前缀"a" 和后缀"a"相同且长度为1
    • next[5]=2,表示索引5之前的子串"abcab"其中前缀"ab"和后缀"ab"相同且长度为2
    • next[6]=1,表示索引6之前的子串"abcaba"其中前缀"a"和后缀"a"相同且长度为1
  • 综上所述,next[]的值(除去next[0])实际上表示的是该位置字符之前的子串 所含有的最大相同前后缀的 长度

  • 解释next[]数组作用:

    • 在KMP匹配中,一旦 i 和 j 停下,说明前面的子串是匹配成功的,即i-1和j-1指向的字符相同,i-2和j-2指向的字符相同…

    • 如果此时next[j]的值不为0,说明前面的子串存在相同的前后缀,则表示文本串中最末尾的几个(后缀)可以与模式串中最前面的几个(前缀)完全匹配

    • 例:(因为该例next[j]=2则表示相同前后缀长度为2,而数组下标均从0开始,所以回退到的下标也是2)
      在这里插入图片描述

    • 则 j 不必回退到0的位置,而是相同前缀的后一个位置即 next[j] 开始与 i 指向的字符继续比较

最后是 next[] 数组的产生:
  • 可以手算求解orz,方法参见上文
  • 以下是代码(你会发现与KMP代码极其相似!!!因为next[]求解过程就是模式串自己和自己匹配):
void GetNext(char* p,int next[])
{
	int pLen = strlen(p);	//模式串长度
	next[0] = -1;	//直接赋值-1,前面已经解释过
	int i = -1;	//前缀指针
	int j = 0;	//后缀指针
	while (j < pLen - 1)
	{
		if (i == -1 || p[j] == p[i]) 
		{
			i++;
			j++;
			next[j] = i;
		}
		else 
		{
			i = next[i];
		}
	}
}  
  • p[i]==p[j] 后面的步骤很容易理解,表示模式串中前缀和后缀一直匹配成功则i++,j++,并把next[j]的值赋为前缀指针索引值(即为已经匹配好的相同前后缀的长度)
  • 一旦匹配失败,i=next[i] 很难理解(蒟蒻我理解了一整天),前缀指针回退到它所指字符前子串的最大相同前后缀末尾之后
    • 如果 next[i]=0,则表示 i 之前没有相同前后缀,则 i 回退到索引0处(从头开始)
    • 如果 next[i]=2,则表示 i 之前的子串具有长度为 2 的相同前后缀,则 i 回退到 2 处(长度为2,但索引从0开始,所以相同前缀末尾之后的索引也为 2)
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值