C/C++教程

C++题解 KMP字符串

本文主要是介绍C++题解 KMP字符串,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

C++题解 KMP字符串

题目描述

给定一个模式串 \(S\),以及一个模板串 \(P\),所有字符串中只包含大小写英文字母以及阿拉伯数字。

模板串 \(P\) 在模式串 \(S\) 中多次作为子串出现。

求出模板串 \(P\) 在模式串 \(S\) 中所有出现的位置的起始下标。

输入格式

第一行输入整数 \(N\),表示字符串 \(P\) 的长度。

第二行输入字符串 \(P\)。

第三行输入整数 \(M\),表示字符串 \(S\) 的长度。

第四行输入字符串 \(S\)。

输出格式

共一行,输出所有出现位置的起始下标(下标从 0 开始计数),整数之间用空格隔开。

数据范围

\(1≤N≤105\)
\(1≤M≤106\)

输入样例:

3
aba
5
ababa

输出样例:

0 2

思路

一、初识KMP

KMP介绍

KMP是一种字符串匹配算法,通常我们对于两个字符串匹配,一般使用两个循环嵌套进行暴力求解,很显然时间复杂度为\(O(m*n)\),是非常不乐观的,而KMP的时间复杂度可以优化到\(O(m+n)\)。

KMP算法由D.E.Knuth,J.H.Morris和V.R.Pratt同时发现,因此人们称它为克努特——莫里斯——普拉特操作(简称KMP算法)

KMP相关概念

我们称要匹配的字符串 s[] 为模式串, 类似地,称 p[] 为模板串,同时有一个 next[] 数组。

字符串前一部分 与 字符串后一部分 的一串字符相等时,我们分别称之为 匹配前缀匹配后缀

KMP匹配原理

如上文所述,朴素算法我们使用两个循环暴力匹配,每次匹配失败,将模板串移动一位继续匹配但是这会造成许多无意义的匹配。为了解决这个问题,我们一定想要模板串能向后移动一段较长距离,这样就能减少时间消耗。那么这么最长距离怎么获得呢?

如上图,在匹配失败处的前一串字符,我们可以找到最长 匹配前缀匹配后缀 ,它们是 ab ,匹配失败处前这段字符串我们已经和匹配串匹配了,意味着我们不需要再次匹配,于是我们移动整个模板串向后,使得 匹配前缀 对齐 匹配后缀 ,于是我们可以继续从 匹配前缀 的后一个字符继续开始匹配了,这段操作为我们节省了不少时间。

下面放一个KMP匹配过程,大家再体验一下。

二、NEXT数组

我们对上述 移动模板串使得匹配前缀与匹配后缀对齐 这一行为进行量化,得到的是 NEXT数组。

NEXT 数组的含义即:当前位置的字符不匹配时,我们应该返回到哪个位置继续进行匹配。

NEXT数组的构造

在这里举个例子,对于模板串 ababc ,我们构造得出以下 NEXT 数组:

注意:此处模板串数组下标从1开始

字符 a b a b c
下标 1 2 3 4 5
返回位置 0 1 1 2 3

这里先贴出NEXT构造函数的代码,此处模板串数组下标从1开始

for (int i = 2, j = 0; i <= n; i ++ )
{
    while (j && p[i] != p[j + 1]) j = ne[j];
    if (p[i] == p[j + 1]) j ++ ;
    ne[i] = j;
}

此处以对 next[n + 1] 求解举例,我们求解 next[n + 1] ,说明对于 next[1]...next[n] 我们肯定是已知的。

很显然我们有三种情况:

1. n + 1 == next[n]

这种情况很显然, next[n + 1] = next[n] + 1 , 因为每往后一位,最长匹配前后缀 最多只能加1。

2. n + 1 != next[n]

在图中,表现为 a != c ,这时候怎么办呢,我们会匹配 next[next[n]] ,此时匹配 a == a ,发现是匹配的,于是 next[n + 1] = next[next[n]] = 3

为什么会这样呢,我们看向上图,第一次匹配的时候我们发现 粉色 方框内的字符串是 \(n\) 之前的 最长匹配前后缀 ,而我们的 \(n+1\)不能进行情况1那样的对前一个 最长匹配前后缀 的延长,我们退而求其次,发现粉色框中,四个蓝色框是匹配的,因为粉色 = 粉色,而蓝色属于粉色内的 最长匹配前后缀, 所以四个蓝色也相等。我们便如此递推,直到第三种情况。

3. next[n + 1] = 1

如果n + 1前没有 最长匹配前后缀, 那我们只好重头匹配了,于是next[n + 1] = 1

三、总结

KMP算法的精髓在于对已知信息的充分利用,这体现在待匹配串的匹配上面,更用于预处理时自己匹配自己上面,总而言之,KMP算法是非常值得学习的。


代码实现

#include <iostream>

using namespace std;

const int N = 100010, M = 1000010;

int n, m;
int ne[N];
char s[M], p[N];

int main()
{
    cin >> n >> p + 1 >> m >> s + 1;

    for (int i = 2, j = 0; i <= n; i ++ )
    {
        while (j && p[i] != p[j + 1]) j = ne[j];
        if (p[i] == p[j + 1]) j ++ ;
        ne[i] = j;
    }

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

    return 0;
}
这篇关于C++题解 KMP字符串的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!