KMP入门+例题详解

这篇博客继续上篇博客开的坑,把字符串算法里,很重要也很基础的KMP算法总结一下。

简介


KMP算法主要用来处理字符串匹配问题(单模式串)。即给你两个字符串A,B,问是否B是A的子串。如果使用朴素的算法暴力查找,那么时间复杂度为 O(nm),这是我们不能接受的,KMP算法就可以把时间复杂度降到 O(n)

实现方法


这里有两个字符串,A=“abababaababacb”,B=“ababacb”。我们用两个指针 i 和 j 分别表示,A[i-j+1…i]与B[1…j]完全相等。也就是说,i 是不断增加的,随着 i 的增加,j 相应地变化,且 j 满足以 A[i] 结尾的长度为 j 的字符串正好匹配 B 串的前 j 个字符,现在需要检验 A[i+1] 和 B[j+1] 的关系。当 A[i+1] = B[j+1] 时,i 和 j 各+1,当 j=m 时,我们就说 B 是 A 的子串(B串已经完整匹配了),并且可以根据这时的值算出匹配的位置。当 A[i+1] ≠ B[j+1],KMP的策略是调整 j 的位置(减小 j 值)使得 A[i-j+1…i] 与 B[1…j] 保持匹配并尝试匹配新的 A[i+1] 与 B[j+1]。
继续看如下分析
在这里插入图片描述
此时i=j=5,A[6]≠B[6],这表明,此时 j 不能等于 5 了,我们要把 j 改成比它小的值 j’。j’ 可能是多少呢?仔细想一下啊,我们发现,j’ 必须要使得B[1…j]中的头 j’ 个字母和末 j’ 个字母完全相符(这样 j 变成了 j’ 后才能继续保持 i 和 j 的性质)。这个 j’ 当然要越大越好(匹配得尽量长)。在这里,B[1…5]=ababa,头 3 个字母和末 3 个字母都是 aba。而当新的 j 为 3 时,A[6]恰好和B[6]相等。于是,i 变成了 6,而 j 则变成了 4。
在这里插入图片描述

从上面我们就可以看到,新的 j 取多少与 i 无关,只与 B 串有关。我们完全可以预处理出这样一个数组 nxt[j],表示当匹配到 B 数组的第 j 个字母而第 j+1 个字母不能匹配了时,新的 j 最大是多少。nxt[j] 应该是所有满足 B[1…k]=B[j-k+1…j]的k(k<j) 的最大值(即最长的相同前缀和后缀)。
再来,A[7]=B[5],i 和 j 又各增加 1。这时又出现了 A[i+1]≠B[j+1]的情况
在这里插入图片描述
由于 nxt[5]=3,因此新的 j=3
在这里插入图片描述
这时,新的 j=3 仍然不能满足 A[i+1]=B[j+1],此时我们再次减小 j 值,将 j 更新为 nxt[3] ("aba"最长公共前后缀长度为 1,因此nxt[3]=1)
在这里插入图片描述
现在,i 还是 7,j 已经变成 1 了。A[i+1]≠B[j+1]。这样,j 必须减小到 nxt[1],即 0
在这里插入图片描述
终于,A[8]=B[1],i 变为 8,j 为 1。事实上,有可能 j 到了 0 仍然是 A[i+1]≠B[]。因此,准确的说法是,当 j=0 时,我们增加 i 值,但忽略 j 直到出现 A[i]=B[1]为止。
代码如下

void kmp()
{
	int j=0;
	for(int i=0;i<n;++i)
	{
		while(j>0&&B[j+1]!=A[i+1])
		j=nxt[j];//不能继续匹配且j还没减到0,减小j值
		if(B[j+1]==A[i+1])
		++j;//能继续匹配,j+1
		if(j==m)//找到一处匹配
		{
			printf("%d\n",i+1-m+1);//子串串首在母串中的位置
			j=nxt[j];//继续匹配
		}
	}
}

最后的 j=nxt[j] 是为了让程序继续做下去,因为我们可能找到多处匹配,两处匹配可以重叠。
这个程序或许比想象的要简单,因为对于 i 值的不断增加,代码用的是 for 循环。因此,这个代码可以形象地理解:扫描字符串 A,并更新可以匹配到 B 的什么位置。

现在,我们还遗留两个重要的问题:为什么这个程序是线性的?如何快速预处理 nxt 数组?

虽然for循环里嵌套了一层while循环,但是for循环j最多加 n 次,所以while循环最多执行 n 次,所以时间复杂度最坏情况下也是 O(n)。
如果朴素处理 nxt 数组,那么时间复杂度为 O(m2) 甚至 O(m3)。其实我们像刚才模式串匹配的过程一样去预处理 nxt 数组。
例如,B=“ababacb”,假如我们已经求出了nxt[1],nxt[2],nxt[3],nxt[4],看如何求出nxt[5],nxt[6]。
在这里插入图片描述
易知,B[5]=B[3],所以 nxt[5]=nxt[4]+1=3。

在这里插入图片描述
B[6]≠B[4],此时没匹配成功,我们需要将前缀减小,即此时指针应该指向 B[1],即通过 nxt[3],将指针调至 B[1]。因为当前前后缀不相等,所以我们需要看更小的前后缀是否满足,这和之前模式串匹配过程是一样的。
B[6]≠B[2],指针应该指到0了,此时B[6]≠B[1],那么 nxt[6]=0。
在这里插入图片描述
此时,B[7]≠B[1],所以 nxt[7]=0。

代码如下

void getnxt()//求next数组
{
    nxt[1]=0;
    int j=0;
    for(int i=1;i<m;++i)
    {
        while(j>0&&T[j+1]!=T[i+1])//不能继续匹配且j还没减到0,考虑退一步
        j=nxt[j];
        if(T[j+1]==T[i+1])//能匹配,j+1
        ++j;
        nxt[i+1]=j;
    }
}

样例分析
剪花布条(HDU2087)
题目大意
给一个母串,一个模式串,看母串能分割出多少与模式串相同的子串。
解题思路
就是利用KMP,找母串中是否有与模式串相同的子串,找到相同的直接从母串中去掉,从下个位置继续找。在原先KMP的模板上稍作修改即可。
AC代码

#include <iostream>
#include <stdio.h>
#include <cstring>
using namespace std;
const int maxn=1e3+5;
int nxt[maxn];
char S[maxn],T[maxn];
int ans;
void getnxt()//求next数组
{
    int len=strlen(T+1);
    nxt[1]=0;
    int j=0;
    for(int i=1;i<len;++i)
    {
        while(j>0&&T[j+1]!=T[i+1])//不能继续匹配且j还没减到0,考虑退一步
        j=nxt[j];
        if(T[j+1]==T[i+1])//能匹配,j+1
        ++j;
        nxt[i+1]=j;
    }
}
void kmp()//kmp找母串子串有多少模式串
{
    int lent=strlen(T+1);
    int lens=strlen(S+1);
    int j=0;
    for(int i=0;i<lens;++i)
    {
        while(j>0&&T[j+1]!=S[i+1])//不能继续匹配且j还没减到0,减小j的值
        j=nxt[j];
        if(T[j+1]==S[i+1])//能继续匹配j,j的值+1
        j++;
        if(j==lent)//找到一处匹配
        {
            ans++;
            j=0;
        }
    }
}
void init()//初始化
{
    ans=0;
}
int main()
{
    while(~scanf("%s",S+1)&&S[1]!='#')
    {
        scanf("%s",T+1);
        init();
        getnxt();
        kmp();
        printf("%d\n",ans);
    }
    return 0;
}

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
KMP算法(Knuth-Morris-Pratt算法)是一种用于解决字符串匹配问题的高效算法。它的主要思想是利用匹配失败时的信息,尽量减少比较次数,提高匹配效率。 KMP算法的核心是构建一个部分匹配表(Partial Match Table),也称为Next数组。这个表记录了在匹配失败时应该将模式串向右移动的位置。 构建部分匹配表的过程如下: 1. 首先,将模式串中的第一个字符的Next值设为0,表示当匹配失败时,模式串不需要移动; 2. 然后,从模式串的第二个字符开始,依次计算Next值; 3. 当第i个字符与前面某个字符相同的时候,Next[i]的值为该字符之前(不包括该字符)的相同前缀和后缀的最大长度; 4. 如果不存在相同的前缀和后缀,则Next[i]的值为0。 有了部分匹配表之后,KMP算法的匹配过程如下: 1. 用i和j来分别表示模式串和主串的当前位置; 2. 如果模式串中的字符和主串中的字符相同,那么i和j都向右移动一位; 3. 如果模式串中的字符和主串中的字符不同,那么根据部分匹配表来确定模式串的下一个位置; 4. 假设当前模式串的位置为i,根据部分匹配表中的值Next[i],将模式串向右移动Next[i]个位置; 5. 重复上述步骤,直到找到匹配或者主串遍历完毕。 KMP算法的时间复杂度为O(m + n),其中m和n分别是模式串和主串的长度。相比于暴力匹配算法的时间复杂度为O(m * n),KMP算法能够大幅减少比较次数,提高匹配效率。 综上所述,KMP模式匹配算法通过构建部分匹配表并利用匹配失败时的信息,实现了高效的字符串匹配。在实际应用中,KMP算法被广泛地应用于文本编辑、数据搜索和字符串处理等领域。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值