从KMP到AC自动机

不想写题。不如写写算法总结?

KMP

介(che)绍(dan)

以前都不知道\(KMP\)为什么叫\(KMP\),现在才明白:该算法是三位大牛:D.E.KnuthJ.H.MorrisV.R.Pratt同时发现的,以其名字首字母命名。
\(KMP\)可以在\(O(n+m)\)的时间复杂度内解决判定一个字符串\(A[1\)~ \(N]\)是否为字符串\(B[1\)~\(M]\)的字串的问题。
虽然Hash好像也可以线性解决这个问题

我会暴力

当然一个\(O(nm)\)的做法是非常显然的:直接枚举A串在B串的开始位置然后往后一位一位的比较。
考虑这样的做法有什么可以优化的地方?
考虑如下场景,某次匹配中按\(O(nm)\)的方法进行到这一步。
图中下方是串\(A\),上方是串\(B\),我们已经匹配到最后一个字符,匹配就快成功,但不幸的是最后一位出错了,我们又要从头匹配。
但是

我们发现绿框框住的部分匹配,但我们之后还要重新匹配浪费了时间。我们能不能记录一些信息,然后下次直接从绿框后面的位置开始匹配,这样不就节约了时间。
还有,能不能选出一些可能完成匹配的位置进行匹配,这样就不用从每一个位置匹配整个串,也节约了时间。
\(KMP\)算法就这样诞生了

算法流程

KMP算法定义了一个next数组
其中next[i]表示A中以i结尾的非前缀子串A的前缀能匹配的最长长度。
那么这个东西有什么用啊?感觉好玄妙,为什么非要是非前缀?
别急我们先来说说\(next\)数组的求法。
我会\(O(n^2)\)的求法。。。
其实可以\(O(n)\)求。

假设我们已经求出了next 1~next i,图中绿框框起来的就是能匹配的最长的A中以i结尾的非前缀子串A的前缀
我们现在要求\(next[i+1]\)。

(这里的j就相当于\(next[i]\))
显然当两个红圈圈起的位置上的字符相等,那么\(next[i+1]=j+1\)
那么不相等怎么办?
重新匹配吗,那不就\(O(n^2)\)了吗?
我不会了妈妈救我

我们先设出\(next[j]\)的位置。显然两个蓝框框起的串匹配

因为绿框框起的串匹配,是不是四块蓝框框起的串互相匹配。

四块都互相匹配了显然这两块是匹配的。
咦,等等,好像有点眼熟!
这跟开始的一张图片很像。
也许你已经猜到接下来该做什么了。
我们看看\(next[j]+1\)和\(i+1\)位置上的字符是否相等。如果相等\(next[i+1]=next[j]+1\),如果还不相等我们把\(next[j]\)再变成\(next[next[j]]\)。。。。。。(一直这样跳\(next\)跳下去)
知道最后找到一个字符跟\(i+1\)位置上的字符相等。或找不到字符跟\(i+1\)位置上的字符相等\(next[i+1]\)就是0。
这样的正确性有保证吗?
我们想一直跳\(next\)实际上就是在遍历(所有能匹配的A中以i结尾的非前缀子串A的前缀的长度)(名词太长用括号括起来)。因为\(next\)要求是最长长度,所以可以不重不漏遍历所有情况。
或者这样想

如果漏掉了紫色框的情况。即假设紫色框框起的部分是(长度比绿框小的是最长的能匹配的最长的A中以j结尾的非前缀子串A的前缀)

那么因为绿框框起的串匹配,四个紫框框起的串互相匹配。
然后next[j]就不在图中所在位置了,也就是说与\(next[j]\)代表A中以j结尾的非前缀子串A的前缀能匹配的最长长度矛盾。
所以用上面说的方法正确性是对的。
那这样感觉复杂度又成\(O(n^2)\)的了
其实是\(O(n)\)的,我们来分析一波
这是求\(next\)数组的代码

for(int i=2,j=0;i<=len;i++){
    while(j&&A[j+1]!=A[i])j=nxt[j];
    if(A[j+1]==A[i])j++;
    nxt[i]=j;
}

每次求\(next[i]\)时我们程序里的记录\(next[i]\)的变量\(j\)最多+1,一共最多加n次。然后每次跳\(next\),j只会减小。
所以最多跳\(n\)次\(next\)。复杂度\(O(n)\)。
至此我们终于把如何求\(next\)数组讲完了。
其实后面的就简单了。
我们回到最初的起点。。
我们依然是按位匹配两个串。当不匹配的时候。我们把i改成next[i]继续匹配就好。
就像这样。
板子题
# P3375 【模板】KMP字符串匹配

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
const int N=1010000;
char s1[N],s2[N];
int len1,len2,nxt[N];
int main(){
    scanf("%s",s1+1);
    scanf("%s",s2+1);
    len1=strlen(s1+1);
    len2=strlen(s2+1);
    for(int i=2,j=0;i<=len2;i++){
        while(j&&s2[j+1]!=s2[i])j=nxt[j];
        if(s2[j+1]==s2[i])j++;
        nxt[i]=j;
    }
    for(int i=1,j=0;i<=len1;i++){
        while((j&&s2[j+1]!=s1[i])||j==len2)j=nxt[j];
        if(s2[j+1]==s1[i])j++;
        if(j==len2)printf("%d\n",i-j+1);
    }
    for(int i=1;i<=len2;i++)printf("%d ",nxt[i]);
    return 0;
}

持续更新ing。。。

原文地址:https://www.cnblogs.com/Xu-daxia/p/10538668.html

时间: 2024-10-17 11:08:42

从KMP到AC自动机的相关文章

KMP,Trie,AC自动机题目集

字符串算法并不多,KMP,trie,AC自动机就是其中几个最经典的.字符串的题目灵活多变也有许多套路,需要多做题才能体会.这里收集了许多前辈的题目做个集合,方便自己回忆. KMP题目:https://blog.csdn.net/qq_38891827/article/details/80501506 Trie树题目:https://blog.csdn.net/qq_38891827/article/details/80532462 AC自动机:模板https://www.luogu.org/bl

字符串匹配相关模板(字典树、KMP、AC自动机)

字典树 struct Trie { int ch[MAXN][26]; int cnt; Trie() { cnt=1; memset(ch[0],0,sizeof(ch[0])); } int idx(char c) { return c-'a'; } void insert(char *s,int v) { int u=0,len=strlen(s); for (int i=0;i<len;i++) { int c=idx(s[i]); if (!ch[u][c]) { memset(ch[

woj1572 Cyy and Fzz KMP / AC自动机 + DP

题目:http://acm.whu.edu.cn/land/problem/detail?problem_id=1572 题意:  有n个目标串,长度均小于15,(n<=8),现在随机写一个长度为L的字符串,问写下的这个字符串包含目标串的期望的个数. 比赛的时候还以为是水题,其实是自己太水.这种题一般是AC自动机的中等题,本题也可以用KMP做,结合状压dp. 方法一:AC自动机 建完Trie树后,就是跑一遍dp,注意单词节点要 |=(1<<val),会有重的字符串. dp过程: 用 dp

AC自动机及KMP练习

好久都没敲过KMP和AC自动机了.以前只会敲个kuangbin牌板子套题.现在重新写了自己的板子加深了印象.并且刷了一些题来增加自己的理解. KMP网上教程很多,但我的建议还是先看AC自动机(Trie图)的构造后再去理解.板子的话大家大同小异. 而AC自动机的构造则是推荐王贇的<Trie图的构建.活用与改进>. 前面的备用知识则是字典树.推荐董华星的<浅析字母树在信息学竞赛中的应用>.董聚聚不仅仅是介绍了字典树,包括一些常见的应用也有论述,介绍的挺详细的. 接下来就是刷题的部分了.

利用AC自动机进行关键字的提取和过滤

昨天看了meituan.com的AC算法在美团上单系统的应用一文,深受启发,原来ACM算法在工程中也能有这样赤裸裸的运用~~~ 于是便复习了AC自动机,并把代码用java重新搞了一遍~~ AC自动机整体的结果大概是长这样的,其实就是在trie树上做KMP : AC自动机里面比较难理解的应该是它的失配指针的计算过程. 这个计算过程从本质上讲就是进行一遍广搜,于此同时维护 fail指针,每一步的维护过程可用下图表示. Keyword.java package com.AC.domain; impor

数据结构14——AC自动机

一.相关介绍 知识要求 字典树Trie KMP算法 AC自动机 多模式串的字符匹配算法(KMP是单模式串的字符匹配算法) 单模式串问题&多模式串问题 单模就是给你一个模式串,问你这个模式串是否在主串中出现过,这个问题可以用kmp算法高效完成: 多模就是给你多个模式串,问你有多少个模式串在这个主串中出现过. 若我们暴力地用每一个模式串对主串做kmp,这样虽然理论上可行,但是时间复杂度非常之高.而AC自动机算法就能高效地处理这种多模式串问题. 二.算法实现 [打基础] 失配指针fail 每个节点都有

【BZOJ3940】【Usaco2015 Feb】Censoring AC自动机

链接: #include <stdio.h> int main() { puts("转载请注明出处[vmurder]谢谢"); puts("网址:blog.csdn.net/vmurder/article/details/44960463"); } 题意: 题意同BZOJ3942,不过要删除的串是多串 http://blog.csdn.net/vmurder/article/details/44959895 题解: --思路一模一样,除了不用kmp用AC

AC自动机学习小结

AC自动机 简要说明 \(AC\) 自动机,全称 \(Aho-Corasick\ automaton\) ,是一种有限状态自动机,应用于多模式串匹配.在 \(OI\) 中通常搭配 \(dp\) 食用.因为它是状态自动机. 感性理解:在 \(Trie\) 树上加上 \(fail\) 指针.具体的讲解可以去看dalao们的博客(因为我实在是太菜了讲不好). 题目 Keywords Search 题目:给若干个模式串,再给一个文本串,问有几个模式串在文本串中出现过. 板子题.注意一个模式串只被计算一次

AC自动机详解

首先,看清楚了,这是AC自动机不是自动AC机 引用AC自动机模板题上的一句话: ovo 在学习AC自动机之前,应该先掌握一下两个前置技能: Trie KMP AC自动机,通俗地讲,就是在Trie上跑KMP.AC自动机利用Trie的性质和KMP的思想,可以实现字符串的多模匹配.KMP是单模匹配,而它与AC自动机最大的区别就在fail指针的求法,其余思想基本相同. 所谓多模匹配,即给出若干个模式串和一个文本串,需要查找这些模式串在文本串中出现的情况. AC自动机的操作分为三步: 建树 求fail指针