首页 > 其他分享 >自动机相关

自动机相关

时间:2023-05-25 20:58:43浏览次数:39  
标签:Trie 节点 Fail 相关 自动机 DFA 转移

前言

以下内容大多摘抄自 OI-Wiki 以及 \(\text{Alex\_Wei}\)----自动机相关 还有我 董晓 大爹。

确定有限状态自动机(DFA)

形式化定义

一个 确定有限状态自动机(DFA) 由以下五部分组成:

  • 1.字符集(\(\Sigma\)) , 该自动机只能输入这些字符

  • 2.状态集合(\(Q\)) , 如果把一个 \(DFA\) 看成一张有向图,那么 \(DFA\) 中的状态就相当于图上的顶点。

  • 3.起始状态(\(start\)) , \(start\in Q\),是一个特殊的状态。起始状态一般用 \(s\) 表示,为了避免混淆,本文中使用 \(start\)。

  • 4.接受状态集合(\(F\)) , \(F \in Q\) , 是一组特殊的状态。

  • 5.转移函数(\(\delta\)) , \(\delta\) 是一个接受两个参数返回一个值的函数,其中第一个参数和返回值都是一个状态,第二个参数是字符集中的一个字符。如果把一个 \(DFA\) 看成一张有向图,那么 \(DFA\) 中的转移函数就相当于顶点间的边,而每条边上都有一个字符。

\(DFA\) 的作用就是识别字符串,一个自动机 \(A\),若它能识别(接受)字符串 \(S\),那么 \(A(S)=\mathrm{True}\),否则 \(A(S)=\mathrm{False}\)。

当一个 \(DFA\) 读入一个字符串时,从初始状态起按照转移函数一个一个字符地转移。如果读入完一个字符串的所有字符后位于一个接受状态,那么我们称这个 \(DFA\) 接受 这个字符串,反之我们称这个 \(DFA\) 不接受 这个字符串。

如果一个状态 \(v\) 没有字符 \(c\) 的转移,那么我们令 \(\delta(v,c)=\mathrm{null}\),而 \(\mathrm{null}\) 只能转移到 \(\mathrm{null}\),且 \(\mathrm{null}\) 不属于接受状态集合。无法转移到任何一个接受状态的状态都可以视作 \(\mathrm{null}\),或者说,\(\mathrm{null}\) 代指所有无法转移到任何一个接受状态的状态。

我们扩展定义转移函数 \(\delta\),令其第二个参数可以接收一个字符串:\(\delta(v,s)=\delta(\delta(v,s[1]),s[2..|s|])\),扩展后的转移函数就可以表示从一个状态起接收一个字符串后转移到的状态。那么,\(A(s)=[\delta(start,s)\in F]\)。

如,一个接受且仅接受字符串 "a", "ab", "aac" 的 \(DFA\):

常见自动机

字典树

字典树接受且仅接受指定的字符串集合中的元素,说白了就是你输入啥它就怎么建树。

转移函数就是 \(Trie\) 上的边,接收状态是将每个字符串插入到 \(Trie\) 时到达的那个状态。也就是常用的 \(en[i]\) 数组。

ACAM AC自动机

AC 自动机也是一类常用的DFA,有完整的 DFA 五要素,分别是起点 \(start\)(Trie 树根节点),状态集合 \(Q\)(Trie 树上所有节点),接受状态集合 \(F\)(所有以某个单词作为后缀的节点,也就是\(en\)所在的位置),字符集 \(\Sigma\)(题目给定)和转移函数 \(\delta\)(类似 KMP)。

解决问题

AC 自动机用于解决 多模式串 匹配问题:给定多个模式串 \(s_i\) 和一个文本串 \(t\) ,求 \(s_i\) 在 \(t\) 中出现的次数。由此可以看出,AC自动机和KMP的区别就在于模式串的多少。

算法详解

如果直接用 KMP 进行暴力,复杂度是 \(|t| \times N + \Sigma |s_i|\) ,其中 \(N\) 是单词个数。如果文本串 \(|t|\) 过长,那复杂度肯定就寄了。

步骤1:建 trie 树

这部分的操作就是用模式串建立字典树来进行操作。

步骤2:Fail指针的建立

类似于 KMP 中的 \(next\) 数组,感谢董晓大爹。

用白话说,Fail 指针的定义是:如果一个点 \(i\) 的 Fail 指针指向 \(j\) ,那么 \(root\) 到 \(j\) 的这个字符串就是 \(root\) 到 \(i\) 的字符串在这个 Trie 树里的最长后缀

废话不多说,放图:

图中 \(Fail[6] = 2 , Fail[7] = 3\),这就是 Fail 指针的定义,下面我们考虑如何建立 Fail 指针。

  • 首先我们要知道两个点

    一、\(Trie[u][i]\) 可能有两种含义:

    1.存节点 \(u\) 的树边的终点。如图中 \(ch[6][e] = 7\) ;

    2.存节点 \(u\) 的转移边的终点,如 \(ch[7][r] = 4\) ,因为这种边在图中太多了,就没有表示出来。

    二、这棵 Trie 树包含三种边,回跳边转移边树边

    (1)树边:树边就是我们在第一步建立 Trie 树形成的边;

    (2)回跳边:回跳边指向父节点的回跳边所指节点的儿子,四个点(\(u,v,fail[u],trie[fail[u]][v]\))构成一个四边形。实际上我们就是通过这个思想来构建 Fail 指针的。回跳边的方向就是 Fail 指针的方向,并且这种边并不实际存在。所以我们可以发现 \(Fail[i]\) 就是存的节点 \(i\) 的回跳边的终点;

    (3)转移边:转移边指向当前节点的回跳边所指节点的儿子,三个点(\(u,fail[u],trie[fail[u][v]\))构成一个三角形。我们通过建立这种边来完成最后文本串的遍历。

那么什么时候建回跳边,什么时候建转移边呢?我们看图说话。

如图:

当 \(u\) 真实存在一个儿子节点 \(v\) 时,我们就建立回跳边来更新我们的 \(Fail\) 指针。那如果说不存在 \(Trie[fail[u]][v]\) 这个点怎么办?先不急,我们先看怎么建转移边。

如图:

当 \(u\) 不存在一个真实的儿子节点 \(v\) 时,因为 \(u\) 和 \(Fail[u]\) 所处的位置都是同一个字母,所以其实它们是等价的,\(Fail[u]\) 存在一个儿子 \(v\) ,那么 \(u\) 也能存在一个儿子 \(v\) ,我们建立一个转移边。通过这样的连边,我们就能完成从一个分支上跳到另一个分支上的功能了。那如果 \(Trie[fail[u]][v]\) 也不存在呢?那么 \(u\) 就会通过这条转移边一直往上转移,知道找到一个真正的 \(v\) 点,或是一直转移到 \(root\) 也不存在,那么就会连向 \(root\)

怎么理解呢,还是看图:

我们想给最下面的 \(v\) 建立 Fail 指针,就需要它的父亲 \(u\) 的回跳边所指节点的儿子 \(v\) ,可是 \(Fail[u]\) 并没有一个真实的子节点 \(v\) , 但是 \(Fail[Fail[u]]\) 有啊,并且在这之前我们已经建立了 \(Fail[u]\) 连向 \(Trie[Fail[Fail[u]]][v]\) 的一条转移边
,我们就能得到

\(Trie[Fail[u][v] = Trie[Fail[Fail[u]]][v]\) 了,那么又因为回跳边的定义

\(Fail[v] = Trie[Fail[u]][v]\) ,

我们最终便能得出 \(Fail[v] = Trie[Fail[Fail[u]]][v]\),这就是我说的不断向上转移的过程。由此,刚才我提出的不存在 \(Trie[u][v]\) 这个点怎么建立回跳边的问题,就这么解决了。

如果你还是不能理解,一个个对应一下,仔细一点就好了。

通过上述的过程我们可以看出,回跳边和转移边的建立是有赖于深度较小的点的。因此,我们在建立 Trie 树的时候,是采取的 BFS 的方法来进行操作的。

至此,我们总结一下 \(Fail\) 指针的建立过程。

  • 把深度为 \(1\) 的真实节点入队,因为它们的 Fail 指针肯定指向 root

  • 节点 \(u\) 出队,枚举 \(u\) 的 \(26\) 个儿子

    1.若儿子真实存在,建回跳边,把儿子入队

    2.若非真实存在,节点 \(u\) 自建转移边。

Code:

il void Fail()
{
	queue <int> q;
	for(re int i=1;i<=26;i++) if(trie[0][i]) q.push(trie[0][i]);
	while(!q.empty())
	{
		int u = q.front();
		q.pop();
		for(re int i=1;i<=26;i++)
		{
			if(trie[u][i]) fail[trie[u][i]] = trie[fail[u]][i] , q.push(trie[u][i]);
			else trie[u][i] = trie[fail[u]][i];
		}
	}
}

步骤3:查询单词出现个数

扫描文本串,依次取出字符 \(s_i\)

  1. \(i\) 指针走主串对应的节点,沿着 树边 或者是 转移边 走,保证不回退。

  2. \(j\) 指针沿着回跳边搜索模式串,每次从当前节点走到根节点,把当前节点中的所有后缀模式串 一网打尽,保证不漏解。

  3. 扫描完主串,返回答案。

(董晓大爹的评论:"算法一边走文本串,一边把当前穿的所有后缀串搜出来,实在是强。")

Code:

il void search(string s)
{
	int pos=0,len=s.size();
	for(int i=0;i<len;i++)
	{
		int ch=s[i]-'a'+1;
		pos=trie[pos][ch];
		for(int j=pos;j;j=fail[j]) ++cnt[en[j]];
	}
}

应用

AC自动机能够与动态规划或者矩阵快速幂等相结合,有着很好的性质。这里就不再一一赘述(因为我也不会/kk)

SAM后缀自动机

不会,咕咕

PAM回文自动机

不会,咕咕

标签:Trie,节点,Fail,相关,自动机,DFA,转移
From: https://www.cnblogs.com/bloodstalk/p/17432889.html

相关文章

  • 01、虚拟机相关
     四、虚拟机快照通过拍摄虚拟机快照,可以完全克隆一份相同的已配置好的虚拟机,方便以后使用,免除重复创建系统的麻烦;虚拟机快照会存在IP冲突的情况;步骤1、关机--拍摄快照2、克隆主机3、IP冲突解决--改IP地址改最后一位3-254之间的任一数字   #查看网卡名......
  • MySQL快速安装配置及相关命令
    安装下载https://dev.mysql.com/downloads/mysql/配置,解压并建立初始化配置文件my.ini,内容如下:[mysqld]#设置3306端口port=3306#设置mysql的安装目录basedir=D:\mysql-8.0.32-winx64#设置mysql数据库的数据的存放目录datadir=D:\mysql-8.0.32-winx64\data#允......
  • c# 集合相关的类
    c#中的队列和栈Queue代表这一个先进先出的对象集合,当你需要对各项进行先进先出访问时,可使用Queue队列。反之使用Stack。不建议将Queue类用于新开发。相反,我们建议使用泛型Queue<T>类。使用ConcurrentQueue<T>或ConcurrentStack<T>如果需要同时从多个线程访问集合。......
  • Go语言中的数组以及其相关特性
    在Go语言中,数组是一种固定长度、相同类型元素的序列。可以将数组视为一个盒子,其中每个元素都有自己的位置(索引)和值。数组的长度是在声明时指定的,一旦定义后,其长度将是固定的,不能动态改变。数组的类型由元素类型和长度决定,例如,[5]int表示一个包含5个整数元素的数组。要声明和初始......
  • 关于计算机的一些相关知识
    计算机硬件定义定义:一些物理装置按系统结构的要求构成一个有机整体为计算机软件运行提供物质基础。计算机硬件组成:CPU、主板、内存、电源,主机箱、硬盘、显卡、键盘,鼠标、显示器等。冯.诺依曼体系结构包括:输入设备、存储器、运算器、控制器、输出设备,数据流、指令流、......
  • 衡量搜索相关性
    [Howintheheckdoyoumeasuresearchrelevance?](https://www.reddit.com/r/RedditEng/comments/te0gfz/how_in_the_heck_do_you_measure_search_relevance/)[MeasuringSearchRelevance,Part2:nDCGDeepDive](https://www.reddit.com/r/RedditEng/comments/y6idrl/......
  • 烟雨黑帽程序定制开发反向代理泛目录站群程序-自动相关词+自动采集+文章拼音繁体伪原
     我是烟雨QQ81047380,演示下客户最新定制程序,本套泛目录站群程序适用于反向代理租目录时使用,也可以使用域名做目录站群。 泛目录站群程序功能介绍:1.自动相关词,比如页面关键词是”淡水龙虾养殖“,会自动匹配相关关键词:淡水龙虾养殖技术,淡水龙虾养殖周期多久,澳洲淡水龙虾养......
  • 3.docker容器相关命令
    docker容器相关命令容器是根据镜像创建的dockerimages1.查看容器dockerpsdockerps-a#查看所有容器2.创建容器dockerrun-itd--nameXXX nginx/bin/bashdockerrun-it-namexxxxxcentos:7参数:-i:一直运行着     -t: 给容器分配一个终端,  ......
  • 2.docker 镜像相关命令
    docker镜像相关命令1、查看 dockerimages dockerimages-q#查看所有镜像ID[root@localhost~]#dockerimagesREPOSITORYTAGIMAGEIDCREATEDSIZEnginxlatestf9c14fe76d503hoursago......
  • 1、docker 服务相关命令
    docker服务相关命令1.启动docker服务systemctlstartdocker2.查看docker 状态systemctlstatusdocker3.重启docker  服务systemctlrestartdocker4.停止docker  服务systemcltstopdocker5.设置开机自启服务systemctlenabledocker......