• $KMP$ 算法


    (KMP) 算法

    (1.) 算法概述

    给定长度为 (n) 的文本串 (text),以及长度为 (m) 的模式串 (pattern)

    (KMP) 算法可以在 (O(m)) 时间内预处理出 (pattern) 的所有真前缀和真后缀中最大的公共长度

    进而在 (O(n + m)) 时间内判断 (text) 中是否出现 (pattern),并求出 (pattern)(text) 中出现的所有位置

    (2.) 算法详解

    (2.1) 真后缀与真前缀

    不包含串本身的后缀叫做真后缀

    不包含串本身的前缀叫做真前缀

    (2.2) 蛮力算法的局限性

    蛮力算法的局限性在于,每次失配,都要重头再来匹配,如下图所示

    (2.3) (KMP)

    一个显然的优化如下

    经过观察发现,这样的优化是基于如下事实

    对于字符串 (abca),其所有真前缀和真后缀中最大的公共长度为 (1),相应的真前缀与真后缀均为 (a)

    所以我们不必从头匹配,只需要将 (pattern[1] = a) 的后一项,即 (pattern[2] = b) 和模式串中的 (b) 继续匹配即可

    更加普遍的,示例如下

    如图所示,(text[i])(pattern[j + 1]) 失配,而绿色部分为相同的字符串,所以只需要将 (j) 指向 (j') 即可继续匹配

    利用好真后缀与真前缀的信息,便是 (KMP) 的关键所在

    代码如下

    int L1 = strlen(text + 1);
    for(int i = 1, j = 0; i <= L1; ++i) {
        while(j && text[i] != pattern[j + 1]) j = Next[j];
        if(text[i] == pattern[j + 1]) ++j;
        if(j == L2) printf("%d
    ", i - L2 + 1);
    }
    

    (2.4) (Next) 数组

    (Next) 数组用于求解 (pattern) 的所有真前缀和真后缀中最大的公共长度

    (pattern = abcabce),对应的 (Next) 数组示例如下

    考虑求解 (Next) 数组

    首先初始化 (Next[1]),令 (Next[1] = 0)

    基于动态规划的思想,若 (pattern[i] = pattern[j+1]),那么 (Next[i] = Next[j] + 1)

    (pattern[i] eq pattern[j]),只需要令 (j = next[j]) 即可

    代码如下

    int L2 = strlen(pattern + 1);
    Next[1] = 0;
    for(int i = 2, j = 0; i <= L2; ++i) {
        while(j && pattern[i] != pattern[j + 1]) j = Next[j];
        if(pattern[i] == pattern[j + 1]) Next[i] = ++j;
    }
    

    (3.) 代码

    #include <bits/stdc++.h>
    #define fi first
    #define se second
    #define pii pair<int, int>
    #define pb push_back
    #define dbg(a, l, r) for(int i = l; i <= r; ++i) printf("%d%c", a[i], " 
    "[i == r])
    typedef long long LL;
    typedef unsigned long long ULL;
    const LL INF = 0x3f3f3f3f3f3f3f3f;
    const LL MOD = 11092019;
    const int inf = 0x3f3f3f3f;
    const int DX[] = {0, -1, 0, 1, 0, -1, -1, 1, 1};
    const int DY[] = {0, 0, 1, 0, -1, -1, 1, 1, -1};
    const int N = 1e6 + 7;
    const double PI = acos(-1);
    const double EPS = 1e-6;
    using namespace std;
    using namespace std;
    
    inline LL read() //读入函数
    {
        char c = getchar();
        LL ans = 0, f = 1;
        while(!isdigit(c)) {if(c == '-') f = -1; c = getchar();}
        while(isdigit(c)) {ans = ans * 10 + c - '0'; c = getchar();}
        return ans * f;
    }
    
    char text[N], pattern[N];
    int Next[N];
    void KMP(char *text, char *pattern, int *Next)
    {
        int L1 = strlen(text + 1), L2 = strlen(pattern + 1);
        Next[1] = 0;
        for(int i = 2, j = 0; i <= L2; ++i) {
            while(j && pattern[i] != pattern[j + 1]) j = Next[j];
            if(pattern[i] == pattern[j + 1]) Next[i] = ++j;
        }
        for(int i = 1, j = 0; i <= L1; ++i) {
            while(j && text[i] != pattern[j + 1]) j = Next[j];
            if(text[i] == pattern[j + 1]) ++j;
            if(j == L2) printf("%d
    ", i - L2 + 1);
        }
        dbg(Next, 1, L2);
    }
    int main()
    {
        scanf("%s %s", text + 1, pattern + 1);
        KMP(text, pattern, Next);
        return 0;
    }
    
  • 相关阅读:
    SQLite的总结与在C#的使用
    Linq中比较字符串类型的日期
    C#中委托,匿名函数,lamda表达式复习
    MYSQL中SUM (IF())
    C#在属性中用Lambda语法
    Mysql绿色版安装和遇到的问题
    FormsAuthentication权限管理
    存储过程中高性能安全式SQL拼接
    JavaScript实现搜索联想功能
    JavaScript组成
  • 原文地址:https://www.cnblogs.com/ChenyangXu/p/12920218.html
Copyright © 2020-2023  润新知