1.10总结

KMP算法

KMP算法就是省略了朴素算法中不必要的回溯。在查找字符串前,先对需要查找的字符串做一个分析,这样可以大大减少我们查找的难度,提高查找的速度。

next数组值的推导

我们把需要查找的字符串j值的变化定义为一个数组next

next[j]=0 当j=1时
next[j]=Max{k|1<k<j,且 当次集合不为空时
next[j]=1 其他情况

举几个栗子

(1)T=“abcdex” (如下表所示)

j

123456

模式串T

abcdex

next[j]

011111

①当j=1时, next[1]=0。

②当j=2时,j由1到j-1就只有字符“a”,属于其他情况next[2]=1。

③当j=3时,j由1到j-1串是“ab”,显然“a”与“b”不相等,属其他情况, next[3]=1。

④以后同理,所以最终此T串的next[j]为011111。

(2)T=“abcabx” (如下表所示)

j

123456

模式串T

abcabx

next[j]

011123

①当j=1时, next[1]=0。

②当j=2时,同上例说明, next[2]=1。

③当j=3时,同上, next[3]=1。

④当j=4时,同上, next[4]=1。

⑤当j=5时,此时j由1到j-1的串是“abca”,前缀字符“a”与后缀字符“a”

相等(前缀用下画线表示,后缀用斜体表示),因此可推算出k值为2(由得到p₁=p₄)因此next[5]=2。

⑥当j=6时,j由1到j-1的串是“abcab”,由于前缀字符“ab”与后缀“ab”相等,所以next[6]=3。

我们可以根据经验得到如果前后缀一个字符相等,k值是2,两个字符相等k值是3,个字符相等k值就是n+1。

(3)T=“ababaaaba” (如下表所示)

j

123456789

模式串T

ababaaaba

next[j]

011234223

①当j=1时, next[1]=0。

②当j=2时,同上next[2]=1。

③当j=3时,同上next[3]=1。

④当j=4时,j由1到j-1的串是“aba”,前缀字符“a”与后缀字符“a”相等,next[4]=2。

⑤当j=5时,j由1到j-1的串是“abab”,由于前缀字符“ab”与后缀“ab”相等,所以next[5]=3。

⑥当广=6时,j由1到j-1的串是“ababa”,由于前缀字符“aba”与后缀“aba”相等,所以next[6]=4。

⑦当j=7时,j由1到j-1的串是“ababaa”,由于前缀字符“ab”与后缀“aa”并不相等,只有“a”相等,所以next[7]=2。

⑧当j=8时,j由1到j-1的串是“ababaaa”,只有“a”相等,所以next[8]=2。

⑨当j=9时,j由1到j-1的串是“ababaaab”,由于前缀字符“ab”与后缀“ab”相等,所以next[9]=3。

(4)T=“aaaaaaaab” (如下表所示)

j

123456789

模式串T

aaaaaaaab

next[j]

012345678

①当j=1时, next[1]=0。

②当j=2时,同上next[2]=1。

③当j=3时,j由1到j-1的串是“aa”,前缀字符“a”与后缀字符“a”相等,next[3]=2。

④当j=4时,j由1到j-1的串是“aaa”,由于前缀字符“aa”与后缀“aa”相等,所以next[4]=3。

⑤⋯⋯

⑥当j=9时,j由1到j-1的串是“aaaaaaa”,由于前缀字符“aaaaaaa”与后缀“aaaaaaa”相等,所以next[9]=8。

其实我看完了公式和样例还是一脸懵,没搞懂是怎么推导的,然后搞到了这张动图

计算匹配T的next数组

/*通过计算返回子串T的next数组*/
void get_next(String T,int *next)
{
    int i,k;
    i=1;
    k=0;
    next[1]=0;
    while(i<T[0])
    {
        if(k==0||T[i]==T[k])
        {
            ++i;
            ++k;
            next[i]=k;
        }
        else
            k=next[k];//若字符不相同,则k值回溯
    }
}

匹配查找

/*返回子串T在主串S中的第pos个字符之后的位置。若不存在则返回0*/
/*T非空,1<=pos<=strlen(S)*/
int Index_KMP(String S,String T,int pos)
{
    int i=pos;//i用于主串S中当前位置的下标,从pos位置开始匹配
    int j=1;//j用于子串T中当前位置的下标值
    int next[200];
    get_next(T,next);
    while(i<=S[0]&&j<=T[0])//当i小于S的长度并且j小于T的长度
    {
        if(j==0||S[i]==T[j])//两字母相等继续
        {
            ++i;
            ++j;
        }
        else
        {
            j=next[j];//j退回合适的位置i值不变
        }
    }
    if(j>T[0])
        return i-T[0];
    else
        return 0;
}

KMP算法的改进

对next()函数进行了改良

设取代的数组为nextval

/*求模式串T的next函数修正值并存入数组nextval*/
void get_nextval(String T,int *nextval)
{
    int i,k;
    i=1;
    k=0;
    nextval[1]=0;
    while(i<T[0])//T[0]表示T的长度
    {
        if(k==0||T[i]==T[k])//T[i]表示后缀的单个字符,T[k]表示前缀的单个字符
        {
            ++i;
            ++k;
            if(T[i]!=T[k])
                nextval[i]=k;//如果当前字符与前缀字符不同则k为nextval在i位置的值
              else
                    nextval[i]=nextval[k];//否则就把前缀字符的nextval值赋给nextval在i位置的值
        }
        else
            k=nextval[k];//如果字符不相同就回溯
    }
}

P3375 【模板】KMP字符串匹配

题目描述

给出两个字符串 s1 和 s2,若 s1 的区间 [l,r] 子串与 s2 完全相同,则称 s2 在 s1 中出现了,其出现位置为 l。

现在请你求出s2 在 s1 中所有出现的位置。

定义一个字符串 s 的 border 为s 的一个s 本身的子串 t,满足 t 既是 s 的前缀,又是 s 的后缀。

对于 s2,你还需要求出对于其每个前缀 s'的最长 border t'的长度。

输入格式

第一行为一个字符串,即为 s1。

第二行为一个字符串,即为 s2。

输出格式

首先输出若干行,每行一个整数,按从小到大的顺序输出 s2 在 s1 中出现的位置。

最后一行输出 ∣s2∣ 个整数,第 ii 个整数表示 s2 的长度为 i 的前缀的最长 border 长度。

输入输出样例

输入 #1

ABABABC

ABA

输出 #1

1

3

0 0 1

说明/提示

样例 1 解释

对于 s2 长度为 3 的前缀 ABA,字符串 A 既是其后缀也是其前缀,且是最长的,因此最长 border 长度为 1。

数据规模与约定

本题采用多测试点捆绑测试,共有 3 个子任务

  • Subtask 1(30 points):∣s1∣≤15,∣s2∣≤5。

  • Subtask 2(40 points):∣s1∣≤10^4,∣s2∣≤10^2。

  • Subtask 3(30 points):无特殊约定。

对于全部的测试点,保证 1≤∣s1∣,∣s2∣≤10^6,s1,s2 中均只含大写英文字母。

#include<stdio.h>
#include<string.h>
char s1[1000010],s2[1000010];
int next[1000010];
int len1,len2;
void get_next()
{
    int i,j;
    i=0;
    j=-1;
    next[0]=-1;
    while(i<len2)
    {
        if(j==-1||s2[i]==s2[j])
        {
            i++;
            j++;
            next[i]=j;
        }
        else
            j=next[j];//若字符不相同,则k值回溯
    }
}
void Index_KMP()
{
    int i=0;
    int j=0;
    while(i<len1&&j<len2)//当i小于S的长度并且j小于T的长度
    {
        if(j==-1||s1[i]==s2[j])//两字母相等继续
        {
            i++;
            j++;
        }
        else
        {
            j=next[j];//j退回合适的位置i值不变
        }
        if(j==len2)
        {
            printf("%d\n",i-len2+1);
            j=next[j];
        }
    }
}
int main()
{
    scanf("%s %s",s1,s2);
    len1=strlen(s1);
    len2=strlen(s2);
    get_next();
    Index_KMP();
  for(int i=1;i<=len2;i++)
    printf("%d ",next[i]);
}

我一直尝试用我上面写的代码,但是一直错误,然后我发现了错误就是在定义字符串的时候模板用的是string而我用的是char;并且可以去除Index_KMP()函数中的pos,可以直接在while循环中添加

if(j==len2)

{

printf("%d\n",i-len2+1);

j=next[j];

}

这样就可以从第一个找到的位置继续下一次寻找。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值