学了半天,终于大概搞懂后缀自动机是个什么鬼东西了
这里做一个笔记(以免以后忘掉)
首先我们要注意一个问题:后缀自动机与trie树的形式与构造思想差距均是相对大的,所以不能完全用trie树的角度来理解后缀自动机!!!
然后我们进入正题
基本每个博客都会给出这样一个定义:后缀自动机是一个可以接受这个字符串所有后缀的确定性有限状态自动机
但是这个定义对于初学者而言可以说几乎毫无用处!!!
所以我们换一个角度,直接从后缀自动机的结构形式讲起
后缀自动机,顾名思义,它的作用是识别这个字符串的所有后缀。
所以在构造的时候后缀自动机一定与后缀息息相关。
而且我们知道,一个字符串的一个子串一定是某一个后缀的前缀
所以后缀自动机也具有识别子串的作用。
那么我们举个例子:
接下来所有例子对应的字符串均为ababa
那么首先我们找出它的所有后缀:a,ba,aba,baba,ababa
接下来,我们把这些后缀扔到一棵trie树上(虽然后缀自动机与trie树区别不小,但是从trie树入手更容易理解)
所以我们建成的trie树长这样:
发现什么问题没?
要命啊,长度仅仅为5的串建起来已经长这样了,那要是长度更长空间岂不是会炸飞?
(这个道理很显然,因为一个长度为n的字符串会有n个后缀,而这n个后缀的总长度将是O(n^2)级别的,所以这样构造的时空复杂度均为O(n^2))
但是这显然是无法接受的。
但是我们稍微观察一下,就会发现:下图中用绿色线连起来的节点是完全一样的!所以我们没有必要存两遍!
所以我们压缩一下,就会得到这种东西:
(为了简化,这里只给出了前两个后缀合并后的结果)
但是,现象已经很明显了:加入第二个后缀时,并没有生成新的节点!
这是对空间巨大的优化!
但是请注意,在这里就已经与trie树产生本质区别了!可以发现,这并不是一个树形结构,而是一个图!
所以后缀自动机是字符串自动机中为数不多的图状结构(虽然我只会两种字符串自动机)
当然了,如果我们仍然坚持逐个后缀加入的话,那么时间是无法优化的。
所以接下来就引出了后缀自动机的第一个难点:增量构造法!
我们具体来看一下这个算法的过程。
为了实现这个算法,我们还要再引入一个指针pre,用来记录最长后缀的位置,这一点与AC自动机的fail指针很相似
这个算法的实现方式如下:
首先,我们在根节点上加入第一个字符,此时正常加入即可,蓝色的线代表pre指针
注意到旁边标记了一个len,这个先不用管,一会就知道了
然后加入第二个节点
在加入第二个节点时,我们需要不断向上跳pre指针,跳到的点如果没有这个儿子(即值为‘b’的儿子)则直接将对应点的‘b’儿子指向这个节点即可,这也就是根节点向b连边的原因
(这样连边的原因之一是,字符串中任何一个节点都会是一个后缀的起点,那么为了从根节点出发能识别所有后缀一定要保证根节点向每个对应节点都有连边,但是不一定要为了形成连边产生新的节点)
然后我们再插入下一个节点
不难发现,如果我们直接构造的话,形成的结构应该如上图所示
但是很显然会发现一个问题:根据pre指针的定义,新加入节点的pre指针应当指向上面的'a'而非根节点!
这个问题是如何产生的呢?
我们看到,假设这个串只有aba三个字母,那么根据后缀自动机可以识别子串,子串'a'出现了两次,那么正常来讲,在后缀自动机中应该可以成功识别串“a”两次。
(当然,后缀自动机对子串的识别是基于parent树实现的,这里先不做深入讨论。)