专栏文章

一些对于SAM的理解

算法·理论参与者 1已保存评论 0

文章操作

快速查看文章及其快照的属性,并进行相关操作。

当前评论
0 条
当前快照
1 份
快照标识符
@miqf4cb4
此快照首次捕获于
2025/12/04 03:46
3 个月前
此快照最后确认于
2025/12/04 03:46
3 个月前
查看原文
对于构造需要满足的要求就先省略了。
如果不考虑时空限制的话,后缀Trie是一个很方便的实现方式,但是其时空复杂度都是 O(n2)O(n^2) 的,并不是很能接受。 有两个方向:
  1. 把转移压缩(字符转移变为字串转移),这个对应后缀树。
  2. 压缩状态(找到等价类进行划分),这个对应后缀自动机。
现在我们考虑第二种优化方法,这时我们引入 endpos 的概念,我们用 endpos(t)endpos(t) 表示字符串 tt 在字符串 SS 中出现的所有位置的末位置的集合, endpos 集合相同的子串归为一个等价类。这一定义能带来很多优秀的性质,比较重要的包括:
  1. 对于两个子串 ssttsstt 的关系只有包含或并列两种可能,绝对不会相交。
  2. 一个等价类中的子串长度连续。
  3. 等价类的数量是 O(n)O(n) 级别的。
第三点的证明可以用一种叫做 parent tree 的树,实际上就是根据第 1 条的性质画出的树结构,并且这种结构保证状态数在 O(n)O(n) 级别。
但我认为最巧妙的是后缀自动机的构造,这是一个在线的过程,看起来很不可思议,每动态插入一个字符,就相当于加入一堆新后缀,就要新接许多转移边,就会导致一系列等价类集合更新,似乎不是很能做到快速更改。
然后这里就一直不是很能理解,后来在某处看到了自动地这个词,突然领悟了一些。
我们从来没有维护过 endpos 集合的具体位置,为什么呢,因为这个集合是会自动更新的,当你进行插入时,它就已经改变了,并不需要手动修改。
然后再回过头来看插入操作,最长的那个串 S|S| 一定会形成一个新的等价类 {S}\left \{ |S| \right \} ,而 pp 一路往上跳,如果都不存在到新字符 cc 的转移边,那么这些新形成的子串的 endpos 集合均为 {S}\left \{ |S| \right \},显然是从空集引出的,所以新节点的 linklink 指向源点。假如一路跳到了头,那么就只产生了这个新状态,而转移边已经全连上了。
如果上跳过程中存在一个位置 ppppcc 这条转移边,转移到了 qq ,那么很明显,由 pp 转移到的 qq 的 endpos 集合中会自动加入 S|S| 这一元素,但是,不一定只有 pp 能转移到 qq。因此我们要进行讨论:
  1. 只有 pp 转移到 qq (此时 len(q)=len(p)+1len(q)=len(p)+1),直接在 qq 的 endpos 集合中插入 S|S| 即可,不用进行进一步修改,那我们也只需要将新节点的 linklink 指向 qq 即可完成修改,因为 qq 再向上跳的类中都已经自动加入了 S|S| 这一元素,并不需要我们再沿 qq 跳上去进行修改了,另一种情况也是同理的。
  2. 不只有 pp 转移到 qq,显然需要一个全新的等价类,我们直接新创建一个等价类 cloneclone,很显然地, pp 和从 pp 继续往上跳的且能转移到 qq 的点,都会指向新建节点 cloneclone,因为这才是它们转移到的等价类,而原来 qq 所代表的 endpos 集合就成为了 cloneclone 的真子集,根据定义 linklink 自然要指向 cloneclone,新节点的 linklink 也同理,由于只新增了一个字符,那么加入一个不同字符的转移一定不会有以 S|S| 位置为结尾的后缀,如果存在,那么一定是形如 aaaaa 的串,但很显然这会被情况 1 处理掉,所以 qq 的所有转移放在 cloneclone 上全部成立,至于 cloneclonelenlen,由于只有 pp 及其往上跳的部分点会转移到 cloneclone,所以自然是 lenp+1len_p+1
那么至此插入的三种情况全部解决,只需要把 lastlast 改为新节点的编号就行,插入过程就圆满完成了。
这算是我对后缀自动机的理解过程吧,状态数证明和一些细节的证明还没有深入探究,以后再继续深入。

评论

0 条评论,欢迎与作者交流。

正在加载评论...