以为对于一个数据结构充分的学会使用,必定要对它的构成部分和定义概念有很充分很全面的了解.算法
因此我以为的一种温习的最好方式就是:明晰概念->分析运用这样的层次.数据结构
概念的明晰确实是十分有用的,它既是理解他人算法的必要前提,也是你在看到题目后能够有创新想法的重要基础.spa
下面这篇文章主要是写给笔者本身看的...因此逻辑有点混乱是由于写的顺序不是这样一路下去的...233指针
1.状态集合blog
每一个状态中存储的是一些right集合相同的字符串.初始态的right集合视为{1,2,3...n} [可是例如"aaa"中,"a"的right虽然也是{1,2,3}可是不与初始态重合,这个例子的parent树中初始态只有一个叶子节点].排序
right相同的字符串的话,就不要以为它们之间是没有联系的,它们应该是连续的是相互重叠的.字符串
好比说"abcdefdef中": "abcdef","bcdef","cdef" 就是放在一个right集合中的.
入门
状态中出现了一个属性mx,表示的是这个right集合中线段的最长长度.基础
回顾构造sam的过程,mx的赋值是在实边的基础上赋值的.好比当末尾元素加入的时候mx[np]=mx[p]+1,由于当实边链接的时候表示前一个集合的某些位置能够日后向x的方向拓展一步,那么全部能拓展的子串中选最长的+1就是新的right集合中最长的子串了,而因为咱们是沿着parent往上走,因此parent的前进至关于删去一个首字母获得原先串的一个后缀,因此最底下的串是最长的,因此也能够想象获得新增长的这个节点中的子串必定知足上面说的性质连续的.
变量
若是两个状态u,v,v--x-->u 且 mx[u]=mx[v]+1,则说明u中最长的串能够直接由v的最长串得来.v是全部能经过x到u的串中最长的,其它的串是它的后缀,且不被其余任何串包含.
2.状态之间的联系
1.实边:
若是在一个串的后面加上字符,那么right集合必定发生了改变.
实边链接表示一个right集合的出现,这个right集合应该是前一个right集合中的部分位置日后+1获得的新right集合
例如"abcdeabcdf"中R("abcd")={4,6}--e-->R("abcde")={5}
--f-->R("abcdf")={7}
因此说若是有实边相连说明在串中的某个位置能够接着日后走这个元素到达新的一个状态.
图像感觉就是如今线段上你有不少个小点[表示当前状态的right集合]而后你能够在某些位置后面找到一个元素走一步,变成新的一些小点.
2.parent边:
每一个状态中存下的是一段连续的串,其中最短的那个串去掉首字母就不在这个状态中了.那么就是到了parent节点中.parent(x)就是right集合包含x的全部中最小的集合.
因此parent链往下至关于trans反过来的一个过程:首先你有一个线段上的小点,而后某些位置上你能够往前找到某一个字符,而后知足这个条件的某些小点就构成了parent挂下的right集合.
好比下图中的边就表示的往前加入的一个字符。固然咱们能够发现,加了字符以后能出现新right集合的条件就是你得有几种加法,好比"cd"能够加"b"或者"d";"bcd"能够加"a"或者"d"。
这个的正确性比较显然...若是每一个地方都只能加同样的...固然新串的right集合和本身这个相同咯...
而反过来沿着parent链往上又是集合合并的过程。唔,这个的正确性你能够感觉一下上面那个逆过程,想必是能理解的。
固然咱们在这里就发现了trans和parent的区别。
1.trans是在原来串的基础上日后加字符,parent是往前加。因此一个是right集合中选一部分出来+1做为新的right,一个是直接从right集合中选一部分出来。
2.trans是只要后面能加值就必定能产生新的状态,parent是必需要有至少两条不一样的才能产生新的right集合。
3.状态之间的路径:
1.实边构成的路径
从初始态沿着实边到达某个状态的若干条路径即这个状态中全部的串.若某个状态指的是终止态,那么这是原串的一条后缀.
任意两个状态之间的路径表示的是一个匹配的过程.仍是回到图形上去.一开始你有一条线段上的若干个点,而后你沿着你须要匹配的链,从这些点中选出你须要的点并将位置+1,而后再下一步再选出一些点,再+1...以此下去直到最后你到达的那个状态.其中就是不断选择知足条件的right向后延伸的过程.也至关于一个匹配的过程.
2.parent构成的链
parent构成的链是向上的不断的去掉首字母的过程,其祖先都是它的一个后缀.parent树上不是向上的边构成的路径意义不是很大.可是两个点的lca表示它们的最长公共后缀.
大概反思了一下全部的概念,而后就能够分析一些SAM处理的过程:
1.构造:
构造是在串"S"所构成的SAM的基础上日后拓展一位c获得的SAM.因为SAM须要识别全部的子串,其中不包括c的子串已经获得,须要识别全部包括c的子串,也就是后缀.
首先须要构造一个新的终止态np.由于终止态的最长串天然是整个串,因此mx[np]=mx[p]+1,而后由于是识别后缀,就应该是在原来的后缀基础上向后拓展c.找到全部后缀的方法很简单,首先找到了包括全部后缀的"S",而后沿着parent链往上就是一个不断取后缀的过程.
对于一些本来没有连出c边的后缀,也就是它们在线段上的小点中没有一个位置能向后拓展一步c,如今它们在末尾能够拓展了,因此a[x][c]=np.
对于某些后面本来连出c边的后缀"A",它们在线段上的小点中有某些位置日后走一步c,这样的话还要看是否是有包含了这个后缀而又不是原串后缀的串"B",若是有,那么这个后缀日后走一步c到达的状态的最长串就不是"Ac"而是"Bc",那么"Ac"的right集合会变化,可是"Bc"到"Ac"的这一段的right集合却不会变化.因此须要将这个状态分红两个部分,新的部分的right集合须要加入新的一个点.即fa[np]=nq,这个新的状态的最长长度就是"A"长度+1,即mx[nq]=mx[p]+1;同时也要包含之前的right集合,即fa[q]=nq.再看一下接着往上去的话,由于构成的是一棵parent树,"A"所在的状态至关于一个分叉点,再往上就不会分叉了.因此上面的right都会增长新的那个节点,即fa[nq]=fa[q].既然产生了新的,可能还会有别的后缀也扩展到"A"所扩展的状态去,因此须要把全部fa[x][c]==q的所有改为nq.固然若是知足了mx[q]==mx[p]+1就没有这么麻烦了,只须要给这个right集合扩充就行.fa[np]=q.而后整个过程就完成了.
因此若是说读者您看过我以前有一篇入门的后缀自动机的话,能够发现那张解释构造的图其实有一点小问题.但愿你理解以后能够本身发现.
2.寻找最长公共子串:
首先给第一个串创建sam,而后让别的串在这个自动机上走.
思考这个走的过程.如今咱们在x状态,而后能够日后看是否具备一个当前字符的转移,若是有,就至关于把全部当前状态中能够转移的小点都往前走了一步.若是没有,那么就没法找到这么多匹配的,可是要利用已经匹配的信息,因此咱们能够退回到当前串的后缀去看,而后把已经匹配的长度变成mx[S],至关于咱们舍弃了这个状态到上一个状态的前面的部分,而后再去尝试往x的方向走.固然若是找了全部的,甚至到了初始态都没有x出边,那么就说明没有匹配,已匹配长度设置为0,指针指向初始态.
当咱们处于某个状态的时候,其实并不能判断咱们匹配的是这个状态中的哪个串,可是咱们却知道咱们匹配到了原串的哪几个位置.也就是知道下一个能够往哪一个方向走.若是要知道匹配了多长则须要记录一个变量,可是若是是发现此处不能向后拓展后的沿着parent往回跳,那么就必定能够匹配到parent中的最长的那个串.
寻找多个串的公共子串的时候,能够记录一下每一个节点在每一个位置匹配到的长度,而后对于全部的串在该位置上取一个最小值就是全部串公共的部分了.可是有要注意的地方就是若是你匹配到了某一个状态,那么它的parent中的全部的值其实你也能够匹配到,因此须要拓扑排序处理一下,把每一个匹配到的值往上回溯更新,防止出现疏漏.