DFA , KMP 和 AC自动机
一些废话
更更更好的观感:3ZincBlog。
由于 Github 没法被百度爬到,所以决定 Cnblogs 和 3ZincBlog 同时更新。希望您支持!
DFA:确定有限状态自动机
确定有限状态自动机(DFA,Deterministic Finite Automation),能够按顺序接受一个信号序列并判断其是否符合 DFA 所要判断的条件。
放在 OI 里来说,DFA 一般指:依次输入字符,每输入一次字符就进行一次转移,表示当前字符串从某个状态变到另一个状态,输入完所有字符后,即可直接判断是否符合条件。
将所有状态抽象成点后,把转移当做边,就可以将一个 DFA 看做一张有向图来进行处理。这张有向图中应该有这五部分:
-
,状态集。 中应有该字符串处理时可能存在的所有状态(点)。 -
,字符集。 中有且仅有所有可能的(合法的)输入字符。 -
,起始状态。表示在还未输入时自动机所处的状态(点)。 -
,终点集合。 包含所有可接受的(符合条件的)状态(点)。 -
,转移函数。假设当前处于自动机中的状态(点) ,输入了字符 ,那么转移后的状态(点) ,并且 。简洁的来说, 建立了每个状态(点)间关系,或者说边。设计 是整个程序中最重要的部分。
接下来,我们再重新描述一下 DFA 怎么工作:
-
一开始,当前状态(点)
。 -
现在,每输入一个字符
,于是 并且始终 。 -
输入完字符串,如果
,于是这个字符串是可接受的。不然就不是。
让我们浅尝一下:构造一个 DFA,输入一个二进制串并使它能够判断这个二进制串所对应的数是否偶数。首先,依旧是分五步:
考虑,判断二进制串是否为偶数,只需看最后一位是否为
-
,这是自动机中的两个状态, 表示当前字符串最后一位为 , 表示最后一位为 。 -
,二进制串只有这两个字符。 -
,当然 也行。为什么? -
,表示如果最终停在 ,说明是偶数,可接受。 -
: 。
直观一些:
其中红点表示
从 DFA 的角度看看 KMP
KMP(Knuth Morris Pratt algorithm,用三个提出者的名字命名),可以检测模式串在匹配串中出现了几次。如果我们从 DFA 的角度考虑:接受一个字符串,当且仅当已知的模式串是该字符串的后缀。
我们发现,在加入字符串的过程中,相当于一个模式串前缀不断尝试与匹配串后缀配对的过程。举个栗子,匹配串
(Fluid 真不戳)
我们发现,这个过程可以生动描述一下:固定
写成代码比较简单:(
scanf("%s%s",T+1,S+1);
int n=strlen(T+1),m=strlen(S+1),ans=0;
for(int i=1,j=0;i<=n;i++) {
if(j==m||T[i]!=S[j+1]) j= ;
else j++;
if(j==m) ans++;
}
那么现在的问题是:如何计算不匹配时,
我们继续回到 DFA 的角度来看这个问题:
我们发现,
-
,表示所有 的取值。 -
,表示 和 中所有可能出现的字符。 -
,表示一开始 与 还未开始匹配。 -
,表示只接受 是其后缀的字符串。 -
,表示寻找最大的 使得 且 。
那么现在问题就是,如何快速地求得
由于已知
考虑一个集合
答案是:使用链表的方式存储。因为我们只知道当前的
于是,我们就有:
那么如何求
笔者发现,
于是,我们就有:
于是,我们通过预处理
稍微整理一下,写成程序十分简洁明了:
(将 while
循环转移会更加舒服一下,比较常见)
(这里的
int ans=0;
scanf("%s",t+1); n=strlen(t+1);
if(m==1&&t[1]=='#') return 0;
scanf("%s",s+1); m=strlen(s+1);
for(int i=2,k=0;i<=m;i++) {
while(k&&s[k+1]!=s[i]) k=fail[k];
if(s[k+1]==s[i]) k++; fail[i]=k;
}
for(int i=1,k=0;i<=n;i++) {
while(k&&s[k+1]!=t[i]) k=fail[k];
if(s[k+1]==t[i]) k++;
if(k==n) ans++,k=fail[k];
// 这里有一些问题:若 KMP 匹配的串可以重叠,那么这样写是正确的
// 如果 KMP 匹配的串要完全独立,没有交叉,那么应写成 k=0
}
通过程序,我们来稍微分析一下 KMP 的时间复杂度。
首先,由于每次
对于像笔者这样的菜鸟,完全理解 KMP 要不少时间,但只要多花一些心思,仔细思考一下,KMP 还是比较好理解的。
KMP 的进化:AC 自动机
首先,AC 自动机本名叫做 Aho-Corasick Automaton,而不是 Accepted Automation。所以这个算法不能帮助你自动 AC 题目,而是帮助你处理一些字符串问题。
在上面 KMP 一节中,我们是匹配一个模式串;而 AC 自动机更加强大,可以匹配一堆模式串。其实,简单来说,AC 自动机就是 KMP 与 Trie 树的结合体。
(不知道 Trie 的请出门右转百度搜索)
比如我一个字符串
如果使用传统的 KMP 去做,那么会喜提
而 AC 自动机能够做到的是:
比如上面的例子,我们对
接下来,我们把匹配串放到这棵 Trie 树上跑,跑到红色节点时,相当于进入了一个理想的状态:某个模式串是当前字符串的后缀。此时,这棵 Trie 树就相当于一个 DFA(当然是不完整的):
-
,表示 Trie 树里有哪些节点。 -
,字符集不多解释。 -
,一开始为空串,在根节点处。 -
,表示所有红色节点(结束节点)。 -
,表示从 到 构成的串是自动机上当前字符串能够找到的最长后缀且 具有 儿子。
我们仿照上面 KMP 的做法,也设计一个
(其中
(其中
如果觉得不好理解,可以看一看例子中补全的 DFA:
代码有时间再补吧。
END
感谢您的观看!如果觉得不错的话,向别人推荐一下 3ZincBlog 吧!
笔者不才,语言可能不够简洁、准确,希望大神们帮忙指出。
KMP 部分在老师的讲解上稍做了一些修改。
【推荐】还在用 ECharts 开发大屏?试试这款永久免费的开源 BI 工具!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Windows桌面应用自动更新解决方案SharpUpdater5发布
· 我的家庭实验室服务器集群硬件清单
· C# 13 中的新增功能实操
· Supergateway:MCP服务器的远程调试与集成工具
· Vue3封装支持Base64导出的电子签名组件