「杂谈」感性理解 SAM 结构
2023.2.14 upd:消歧义:下面说的后缀 Trie 指的是隐式后缀树。与 Ukkonen 算法构建的后缀树的区分是没有在每个后缀插入的时候最后加一个特殊字符 #
.
因为是感性理解,重要在于对后缀树及后缀自动机结构的透彻理解。
定义:\(Left(x)\),子串 \(x\) 在母串中出现位置左端点的集合;\(Right(x)\),子串 \(x\) 在母串中出现位置右端点的集合。
在后缀 Trie 中:转移边链接的是自己的最长前缀,fail 边链接的是自己的最长后缀。由于所有的后缀都在其中,所以当前子串的最长前缀实际上就是除去最后一个字符剩下的串,最长后缀也类似。以此可得,正串的后缀 Trie 与反串的 fail 树是相同的。
后缀 Trie 看起来太臃肿了,考虑如何对这个 Trie 进行压缩。
类似压缩 Trie 的思想,如果一个节点只有一个儿子,那么将该节点和儿子合并。由于多一个儿子至少多一个能到达的叶子节点,那么该压缩方法实际上是按照子树内叶子节点的集合来划分等价。那么子树内叶子节点的集合相同在字符串中该如何描述?每一个叶子都代表一个后缀,所以"子树内叶子节点的集合"等价于"包含该串为前缀的原串后缀的集合",这等价于按照 \(Left\) 来划分等价类。以此,我们得到了后缀树结构。
重新观察后缀 Trie,有一些节点子树的结构是一模一样的,那么这些节点可以合并在一起。那么子树相同在字符串中该如何描述?注意到从它开始在 Trie 里面往子孙走的路径都是走完一个后缀,也就是说这些等价类对应的子串,在原串中走到串尾都是同样的过程,这等价于它们的 \(Right\) 等价,也就是说这类压缩方式等价于按照 \(Right\) 来划分等价类。以此,我们得到了后缀自动机的结构。
观察后缀自动机上上的 fail 树(也有的地方称之为 parent 树):注意到 \(Right\) 集合相同的子串,一定是最长的那个串的若干个长度连续的后缀,而且这个长度断开的地方,意味着这个更小的前缀 \(s\) 在其他地方出现过。假设这个等价类中最长的串的不在同一等价类的后缀 \(s\) 前面的字符是 \(c\),则意味着母串中出现了与 \(\overline{cx}\) 不同的 \(\overline{c'x}\) 作为子串,对应在 fail 树上,就是其有多个儿子。同理,如果是在同一等价类的 \(s\),它在母串中仅仅作为 \(\overline{cx}\) 作为子串,对应在 fail 树上,就是其仅有一个儿子。由此可以看出,后缀自动机的压缩同时是对 fail 树进行类似压缩 Trie 的压缩。
考虑到前文所说的正串的 Trie 等价于 反串的 fail,于是得到了 后缀自动机的 parent 树为反串后缀树 这一经典结论。
友谊是魔法!