单模匹配:KMP与扩展KMP

一,KMP 匹配

题面:所谓字符串匹配,是这样一种问题:“字符串 P 是否为字符串 S 的子串?如果是,它出现在 S 的哪些位置?”
约定:其中 S 称为主串;P 称为模式串

朴素思考

枚举每个S中长度为 s t r l e n ( P ) strlen(P) strlen(P)的子串,直接判断字符串相等
在这个过程中,可能直到 l e n ( P ) − 1 len(P)-1 len(P)1 位的时候,串子还是匹配的,不过在最后一位失败了,那么下一次还得重头来,好可惜!
所以使用多余的(无数个小匹配段子)信息,就是 KMP 的精髓

标准思考

要素: n e x t [ i ] next[i] next[i] 数组,代表了:
在 P 中以 i i i后缀(结尾元素) 的长度为 n e x t [ i ] next[i] next[i] 的串和 从头开始的长度为 n e x t [ i ] next[i] next[i] 的串是完全一样的
数学语言: P [ 1 , n x t [ i ] ] = = P [ i − n x t [ i ] + 1 , i ] P[1,nxt[i]]==P[i-nxt[i]+1,i] P[1,nxt[i]]==P[inxt[i]+1,i] l e n = n x t [ i ] len=nxt[i] len=nxt[i]
注意:下面的图片下标从0开始
在这里插入图片描述

在 S[0] 尝试匹配,失配于 S[3] <=> P[3] 之后,我们直接把模式串往右移了两位,让 S[3] 对准 P[1]. 接着继续匹配,失配于 S[8] <=> P[6], 接下来我们把 P 往右平移了三位,把 S[8] 对准 P[3]. 此后继续匹配直到成功。

下一步匹配时,如图中蓝色箭头所示,旧的后缀要与新的前缀一致(如果不一致,那就肯定没法匹配上了)!

回忆next数组的性质:
P [ 0 ] P[0] P[0] P [ i ] P[i] P[i] 这一段子串中,前 n e x t [ i ] next[ i ] next[i] 个字符与后 n e x t [ i ] next[i] next[i] 个字符一模一样。
既然如此,如果失配在 P [ r ] P[r] P[r], 那么 P [ 0 ]   P [ r − 1 ] P[0]~P[r-1] P[0] P[r1] 这一段里面,前 n e x t [ r − 1 ] next[r-1] next[r1] 个字符恰好和后 n e x t [ r − 1 ] next[r-1] next[r1] 个字符相等——也就是说:
我们可以拿长度为 n e x t [ r − 1 ] next[r-1] next[r1] 的那一段前缀,来顶替当前后缀的位置,让匹配继续下去!

代码实现

注意:下标1起步
求解 n x t nxt nxt 数组就是P和P匹配,步骤一致
前置知识:双指针
注意:P配 P 的时候,从第二位开始

int main()
{
    cin>>n>>p+1>>m>>s+1;
    for (int j = 0,i = 2; i <= n; i ++ )
    {
        while (j&&p[j+1]!=p[i])j=nxt[j];
        if(p[j+1]==p[i])j++;
        nxt[i]=j;
    }
    
    for (int i = 1,j=0; i <= m; i ++ )
    {
        while (j&&p[j+1]!=s[i])j=nxt[j];
        if(p[j+1]==s[i])j++;
        if(j==n) printf("%d ",i-j),j=nxt[j];
    }
    
}

阮行止的 KMP_blog
AC Code 及题目

二,扩展KMP(Z 算法)

引入Z数组

  • 表示以 i 开头的后缀和 s 的前缀的最长公共子串(LCP)
  • 特别注意 z[0]=0
  • 和自动机一个意思,需要一个接受和终止状态,用已知的信息缩减未知的部分的运算

流程

  • 双指针 l,r表示已知的右端点最靠右的 z[i]
  • 分成 i,在r的左边和在r的右边

1,r包含i的时候:

在这里插入图片描述
这是绿色段是一个已知的匹配成功的段(上图 a 对应 lp 对于 rnext即为 z 数组)
易知在绿色段内, s [ i ] − s [ p ] s[i]-s[p] s[i]s[p]可以等效为 s [ 0 ] + s [ z [ a ] ] s[0]+s[z[a]] s[0]+s[z[a]],所以他的信息可以等效移植

z [ i ] = m i n ( z [ i − l ] , r − i + 1 ) z[i]=min(z[i-l],r-i+1) z[i]=min(z[il],ri+1)

  • 如果等效的 z [ i − l ] z[i-l] z[il]没到 r r r 的那个相对位置就失配了,那 i i i 必然没到 r r r 就失配
  • 超出 r − i + 1 r-i+1 ri+1的部分暴力判断即可,记得最后更新 r r r l l l

2, i超出r的时候

直接暴力判断即可,记得最后更新 r r r l l l

这是一个模拟过程的web

void SOL_Z(char s[])
{
    int l,r;
    l=r=0;
    rep(i,1,len-1)
    {
        if(i<=r && z[i-l]<r - i + 1)  z[i]=z[i-l];
        else
        {
            z[i]=max(0,r-i+1);
            while(i+z[i]<len && s[i+z[i]]==s[z[i]])z[i]++;
        }
        if(z[i]+i-1>r)r = z[i]+i-1,l=i;
    }
}

解题思路

常用技巧

反串,翻转,拼串,分割,分组

一,单模匹配

  • 把要匹配的串前置,中间用一个分割字符,运行匹配,对于每一个在待匹配串中的位置,只要对应和模式串相同的 Z [ i ] Z[i] Z[i],由于分割字符的存在,必然表示一个匹配成功的位置

二,本质不同子串

  • 定义: 一个字符串的子串中,长度不同或者长度相同但相同位置的字母不同的子串个数
  • 做法:考虑增量
    1,已知一个子串的本质不同子串数,那么新加入一个字符,本质不同子串会加一些
    2,考虑建立新串的反串,求他的height或者z
    3,存在一个 z ( m a x ) z(max) z(max) 使得以新字符 c c c为结尾,长度小于 z ( m a x ) z(max) z(max) 的子串已经出现过了,那么新的本质不同子串个数就是 ∣ t ∣ − z ( m a x ) |t|- z(max) tz(max)
  • 复杂度平方,只是用于思路

三,最小循环节

  • 其整周期的长度为最小的字符串长度的因数 i i i ,满足 z [ i ] + i = = l e n z[i]+i==len z[i]+i==len
  • 满足倍数方能整数的分割,满足后缀前缀拼全方能有循环
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

流苏贺风

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值