后缀自动机学习总结
来源:互联网 发布:php程序员的工作状态 编辑:程序博客网 时间:2024/06/11 03:51
常用的字符串处理工具:
1.
2.
3.
应该说单串索引里面,后缀数组已经非常强大了,已经可以解决很多问题。而后缀自动机我以前都没怎么听说过,网上查了下,好像没有太多资料介绍它,有啥用也没说。貌似很多它能完成的工作,后缀数组也能完成;它不能完成的,后缀数组也能完成。(额,被完爆了)。
尽管如此后缀自动机还是有很吸引人的一面:代码量还不到50行,太短了,太诱人了;而且是O(n)的在线算法,O(1)(均摊)增量式构造。相比之下,后缀数组的最大短板在于不能增量构造,虽然用一些离线的算法可以解决这个问题,但这种trick只在竞赛里面有些用,实际工程里面,该在线的算法还是必须在线。作为一个弱菜,我个人对算法的审美一直都是“简单而有用的东西,就是美的!”,后缀自动机算是一个吧,所以花了一点时间学习。本文主要是个人的总结,详细参考clj的ppt。
我们知道数据结构是一个二元组 = 数据 + 操作。数据结构的灵魂在于,在对数据进行操作的过程当中,保持某些性质不变从而高效的完成任务。这些性质往往分为两种:1. 功能性质; 2. 性能性质。 以平衡二叉树为例:功能性质是左右子树有序,进而实现按key检索、添加、删除;而性能性质是保持左右子树的深度平衡,以实现O(log(n))的操作上限。变化的操作当中保持关键性质不变,个人认为是数据结构设计的核心内容。
再来看后缀自动机,后面的内容主要是个人总结。
后缀自动机的功能性质是什么?
后缀自动机只对后缀感兴趣。对于字符串str,设SAM(str)是其对应的后缀自动机,则SAM(str)接收且仅接收str的所有后缀。也就是说对于str的所有后缀,在SAM(str)里面都有合法的转移,且转移到终态。作为附加功能,使得后缀自动机不仅仅能识别后缀,也能识别str的所有子串。
后缀自动机的性能性质是什么?
str的长度为n,则后缀自动机SAM(str)的状态数为O(n)。因为只有n个后缀,所以状态数为O(n)好像也挺合理,一一对应嘛。但是别忘了,后缀自动机不仅仅能识别后缀,也能识别str的所有子串,这些子串的个数是O(n^2)的,这一下就变得不合理了,O(n)的状态数的如何做到识别O(n^2)个字符串的?在前缀索引里面,以trie树为例,状态和前缀(字符串)是一一对应的,所以trie树的状态数上线刚好等于串的总长度(也是前缀数),可以认为是O(n)的。但是后缀自动机却不能这样干,也来一一对应,这样干的后果就是状态数也变成了O(n^2)。这意味着,要实现O(n)的状态数,就必须使得一些子串被映射到同一个状态,以实现状态的重复利用!这里的问题又产生了?该把哪些串映射到同一个状态?随便搞行吗?
关键概念 + 主要观察:
1.
2.
3
4.
5.
6.
7.
8.
9.
再来理解后缀自动机的构造算法:
SAM(T)到SAM(Tx)需要更新什么?我们的依据是什么?
首先来看我们需要保持哪些性质?
a)
b)
对于a),因为Tx的子串 = T的所有子串+ Tx的所有后缀,因此我们只需要保证Tx的所有后缀有合法转移就行!而Tx的后缀完全是由T的所有后缀增加一个字符x得来的,我们只需要挨个找出T的后缀在SAM(T)中的状态,然后再保证这些状态在SAM(Tx)当中有x转移即可!
如何找出SAM(T)中后缀对应的状态?由于T的所有后缀有一个公共的出现位置r = length(T),这导致他们的状态Right集交集非空,进而根据性质6知道,这些状态肯定是构成一个Parent链,所以要找这些状态最简单的办法就是沿着Parent链回溯!
设SAM(T)当中后缀对应的终态={v1, v2, …….vk},回溯的时候会出现哪些情况呢?
一个是trans(p, x) = null,即不存在x转移,我们就必须增加一个x转移SAM(Tx)的终点np即可,同时保证了性质b);
那q = trans(p, x) != null的时候呢?很好嘛,已经有x转移了,我们只需要保持性质b)就行。扩充Right(q),即把np的parent的指针链向q不就ok了嘛!额。。但这是有问题的!
我们先来看转移到q的前驱有哪些,根据性质9不妨设q有m个前驱:p1, p2, …….,pm,且满足(parent[p1] = p2, parent[p2] = p3, ….)。现在的问题是,p在这条链的哪个位置?如果p = p1(此时step[p] + 1= step[q] ),前面的做法是没有问题的,因为从p2……pm转移到q的所有字符串,必定是从p转移到q的字符串的后缀,所以这些字符串也必定出现在位置length(Tx),因此扩充Right(q) = Right(q) + {length(Tx)}当然没问题!但是,如果p != p1就麻烦了,对于出现在p前面任何一个节点pj,可以证明从pj转移到q的字符串一定不会出现在位置length(Tx)。 如果还是简单的令Right(q) = Right(q) + {length(Tx)},就导致性质b)对节点q失效,因为有些串转移到q但是它的Right对不上! 那如何办呢? 可以看到这里p把前驱分成了两部分,前面一部分转移到q的right集不变;后面一部分的right集应该要扩充。最简单的办法就是把q拆也成两个,对应两部分前驱,这就是构造算法里面的做法!其实理解这个做法的关键点就是性质9 !
- 后缀自动机学习总结
- 后缀自动机学习总结
- 后缀自动机总结
- 后缀自动机总结
- 后缀自动机总结
- 后缀自动机学习资料
- 后缀自动机学习
- 后缀自动机学习
- 后缀自动机学习
- 后缀自动机学习
- 后缀自动机学习小记
- 后缀自动机学习资料
- 学习后缀自动机想法
- 后缀自动机学习小记
- 后缀自动机学习笔记
- 学习一个后缀自动机
- 后缀自动机学习小结
- 后缀自动机学习小记
- HotSpot中Parallel Scavenge/Parallel Old与Serial/Serial Old内存分配策略区别
- Dreamweaver创建列表及查找与替换
- Golang语言学习资源
- mybatis insert & update
- excel基础
- 后缀自动机学习总结
- Eclipse SVN 中的分支与合并
- 如何判断素数 如何输出范围中的素数
- 第一次写cmd命令
- 如何求最大公约数
- 关于activity的一些基础知识
- 自定义天气View控件
- 绕过WAF的SQL注入语句
- 深入JVM之Java内存模型