KMP及其模板

题目描述

给出两个字符串 s 1 s1 s1 s 2 s2 s2,其中 s 2 s2 s2 s 1 s1 s1的子串,求出 s 2 s2 s2 s 1 s1 s1中所有出现的位置。

为了减少骗分的情况,接下来还要输出子串的前缀数组next。

输入输出格式
输入格式:

第一行为一个字符串,即为 s 1 s1 s1

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

输出格式:

若干行,每行包含一个整数,表示 s 2 s2 s2 s 1 s1 s1中出现的位置

接下来 1 1 1行,包括 l e n g t h ( s 2 ) length(s2) length(s2)个整数,表示前缀数组 n e x t [ i ] next[i] next[i]的值。

输入输出样例
输入样例:

ABABABC
ABA

输出样例:

1
3
0 0 1

说明/提示

时空限制: 1000 m s , 128 M 1000ms,128M 1000ms,128M

数据规模:

s 1 s1 s1长度为 N N N s 2 s2 s2长度为 M M M

对于 30 % 30\% 30%的数据: N &lt; = 15 , M &lt; = 5 N&lt;=15,M&lt;=5 N<=15M<=5

对于 70 % 70\% 70%的数据: N &lt; = 10000 , M &lt; = 100 N&lt;=10000,M&lt;=100 N<=10000M<=100

对于 100 % 100\% 100%的数据: N &lt; = 1000000 , M &lt; = 1000000 N&lt;=1000000,M&lt;=1000000 N<=1000000M<=1000000

样例说明:
样例说明
所以两个匹配位置为 1 1 1 3 3 3,输出 1 1 1 3 3 3

算法

一种朴素的算法是对 s 1 s1 s1中的每个字符都尝试与 s 2 s2 s2进行匹配,如果成功则匹配下一个。这种算法的复杂度为 O ( n m ) O(nm) O(nm),显然会超时,我们需要找到另一种方法。

我们发现,对于串 s 1 ‘ ‘ a b a b a b a b c &quot; , s 2 = ‘ ‘ a b a b c &quot; s1``ababababc&quot;, s2=``ababc&quot; s1ababababc",s2=ababc" s 2 s2 s2的前缀 ‘ ‘ a b a b &quot; ``abab&quot; abab" s 1 s1 s1中反复出现了多次,此时再一个一个字母地匹配效率就非常的低。此时我们就需要 K M P KMP KMP算法,使得我们在找到当前字母为 s 2 s2 s2的一个前缀时,不用从头匹配,而是直接从失配处开始匹配。

假定我们现在匹配到 i i i处,在匹配 i + 1 i+1 i+1时失配

在i处失配
此时我们在 s 2 [ 1... i ] s2[1...i] s2[1...i]中寻找最长公共前后缀(即 ‘ ‘ a b a b &quot; ``abab&quot; abab"中的 ‘ ‘ a b &quot; ``ab&quot; ab" 与 ‘ ‘ a b &quot; 与``ab&quot; ab"),并将 s 2 s2 s2移到前缀最后位置,再继续进行匹配。

移动s2
其中 s 2 s2 s2中每个位置 i i i的最长匹配前缀可以预处理为 p r e [ i ] pre[i] pre[i](有的人写为 n e x t [ i ] next[i] next[i])。特别的, p r e [ 0 ] = − 1 pre[0]=-1 pre[0]=1

实现:
匹配:
for (int i = 0, j = -1; i < n; ++i) {						//i、j分别为s1、s2当前下标
    while (j != -1 && A[i] != B[j + 1]) j = pre[j];			//未匹配且j不是-1则令j=pre[j]
    if (A[i] == B[j + 1]) ++j;								//匹配上则++j
    if (j == m - 1) printf("%d\n", i - m + 2), j = pre[j];	//匹配整个串则输出
}
预处理:
pre[0] = -1;										//可以看做s2与自身匹配
for (int i = 1, j = -1; i < m; ++i) {				//i为当前下标,j为前缀最后下标
    while (B[i] != B[j + 1] && j != -1) j = pre[j];	//当前串与前缀的下一个不匹配则令j=pre[j]
    if (B[i] == B[j + 1]) ++j;						//匹配上则++j
    pre[i] = j;										//当前的最长匹配前缀位置为j
}
代码:
#include <bits/stdc++.h>
using namespace std;

int n, m;
int pre[1000005];
char A[1000005], B[1000005];

int main()
{
    scanf("%s%s", A, B);

    n = strlen(A);
    m = strlen(B);

    pre[0] = -1;
    for (int i = 1, j = -1; i < m; ++i) {
        while (B[i] != B[j + 1] && j != -1) j = pre[j];
        if (B[i] == B[j + 1]) ++j;
        pre[i] = j;
    }

    for (int i = 0, j = -1; i < n; ++i) {
        while (j != -1 && A[i] != B[j + 1]) j = pre[j];
        if (A[i] == B[j + 1]) ++j;
        if (j == m - 1) printf("%d\n", i - m + 2), j = pre[j];
    }
    
    for(int i = 0; i < m; ++i)
        printf("%d ", pre[i] + 1);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值