• 后缀自动机浅析




    第一部分 自动机的预备知识

    自动机的功能是识别字符串,一个自动机A,若它能识别字符串S,就记为A(S)=true,否则A(S)=false。 自动机由五个部分组成,字符集alpha,状态集合state,初始状态init,结束状态集合end和状态转移函数trans。 令s,t∈state,ch∈alpha,trans(s,ch)=t表示当前状态是s,在经过一条字符ch的边之后,所到达的状态是t。 如果trans(s,ch)这个转移不存在,那么trans(s,ch)=null。

    第二部分 后缀自动机的预备知识

    后缀自动机,Suffix Automaton Machine,以下简称SAM,是一个能够识别一个串S的所有后缀的自动机,即对于S的后缀自动机A,设S的长度为n,S[0~n-1],S从i位置开始的后缀为Suffix(i),对于S的任意一个后缀Suffix(i),A(Suffix(i))=true。

    定义从SAM的一个状态s能开始能识别的串x,为Reg(s),Reg(s)={x|trans(s,x)∈end}。

    定义ST(str)为trans(init,str),即从初始状态输入串str到达的状态。

    定义串S的后缀的集合为 Suf,子串的集合为Fac,S[l,r)表示从S的第l个字符到第r-1个字符组成的子串。S下标从0开始。显然,若串x属于Fac,那么ST(x)!=null,设x的位置为[l,r),那么x加上Suffic(r)之后就是S的一个后缀,如果ST(x)=null,那么就不能识别这个后缀了,所以ST(x)!=null。

    考虑ST(a)能够识别的字符串,记为Reg(ST(a)),Reg(ST(a))={Suffix(i)|a+Suffix(i)是S的后缀}。所以,Reg(ST(a))是一些后缀的集合。对于串ABBABBBAC ,下标0~8,令a="BBA",我们可以看到a可以在S的多个位置出现。对于一般情况,设a在S中出现的位置集合为

    [{ [{l_1},{r_1}),[{l_2},{r_2}), cdots [{l_n},{r_n})} ]

    那么


    我们定义Right(a),

    [Rightleft( a ight) = { {r_1},{r_2}, cdots {r_n}} ]

    即a串出现的所有结束位置。

    对于两个串a,b∈Fac,如果Right(a)=Right(b),那么ST(a)=ST(b),即从初始状态经过a串和经过b串到达的节点相同。反过来,我们可以说,一个状态s,从初始状态到s的路径有多条,对应多个串,这些串的Right集合相同,<strong>也就是说一个状态,表示了多个Right集合相同的串。</strong>

    对于串a,令r∈Right(a),再给出一个长度len,那么a=S[r-len,r)。设s=ST(a),我们说了s表示的串有多个(包括a),这些串长度的最大最小值为Max(s),Min(s),那么可以证明,s代表的串的个数有Max(s)-Min(s)+1,而且这些串的长度依次是Min(s),Min(s)+1,...,Max(s)。因为s代表的串都是r位置向前某一段的串,s状态代表的串具有相同的后缀(最长的公共后缀其实就是等于Min(s)的那个串),那么从第r-Min(s)的位置向前,每次加一个字符都是s代表的串,直到到达r-Max(s) ,所以它是连续的。

    对于两个状态s1,s2,设他们的Right集合为

    [{R_{s1}},{R_{s2}}]

    设它们有交集,不妨设

    [r in {R_{s1}} cap {R_{s2}}]

    由上面那一段的分析,s1和s2代表的串的集合不会有交集,所以[Min(s1),Max(s1)]和[Min(s2),Max(s2)]没有交集(因为它们都是r向前的某一段),不妨设Max(s1)<Min(s2),也就是说s1中的串都比s2中的串的长度小,那么s1中的串必定都是s2中的串的后缀,因为它们都是r位置向前的某个子串。所以

    [{R_{s2}} subseteq {R_{s1}}]

    所以任意两个Right集合,要么相交,要么一个是另一个的真子集。

    第三部分 后缀自动机的线性证明

    我们来解释下第二部分最后的那个结论。不妨设串为AABBABD,下标标号0到6,我们给出下面这个表



    我们将这个串的所有子串按照Right集合分类,同一类中的子串是Right集合相同的。我们可以看到,它符合第二部分最后的结论:即任意两个Right集合要么无交集,要么一个是另一个的真子集。我们发现,这个性质是一棵树,我们添加一个超级节点S,这个树就是下面这个样子,红色的是节点的Right集合:



    我们可以看到,叶子的个数不超过字符串的长度,中间节点至少有两个孩子,所以中间节点的个数也不超过叶子节点的个数,所以节点总数是O(n)的。

    还有一个疑问,边的个数是O(n)的吗。可能是吧,不过即使是我也不会证明。

    第四部分 后缀自动机的构造方法

    我们构造的方法是,首先创建S的一个前缀T的后缀自动机,然后添加一个字符,然后创建串Tx的后缀自动机,即SAM(T)->SAM(Tx)。SAM(Tx)和SAM(T)相比,多了一些子串,这些子串就是Tx的所有后缀。设T的长度为L。考虑T的所有后缀,也就是Right集合包含L的节点

    [{v_1},{v_2}, cdots {v_k}]

    在这些中存在一个节点p,p=ST(T)。

    [{v_1},{v_2}, cdots {v_k}]

    都是parent树上p的祖先。首先,用节点np表示ST(Tx),Right(np)={L+1}。显然

    [p = {v_1},{v_2}, cdots {v_k} = init]

    考虑其中一个v的Right集合,Right(v)={r1,r2,...,rn=L}。如果Right(v)中不存在i使得 S[ri]=x,那么我们直接连一条v到np的x的边即可,因为v代表的串的集合都是T的后缀,加上x都是Tx的后缀。

    否则,设$v_p$为$v_1,v_2, cdots v_k$中第一个有x的出边的节点。

    [Rightleft( {{v_p}} ight) = { {r_1},{r_2}, cdots {r_n}} ,trans({v_p},x) = q]

    那么q的Right集合为

    [Right(q) = { {r_i} + 1|Sleft[ {{r_i}} ight] = x} ]

    那么现在有两种情况:第一种$Max(q) = Max({v_p}) + 1$,那么直接令np的祖先为q。第二种$Max(q) >Max({v_p}) + 1$,这时候我们新建一个节点nq,$Max(nq) = Max({v_p}) + 1$,nq复制q的孩子信息,原来指向q的边现在连向nq。然后令np的祖先为nq,nq向np连边。

    最后我们总结下创建的过程:令当前串为T,加入字符x。令p=ST(T),新建节点np=ST(Tx),对所有p的祖先节点没有x出边的,向np连一条x的边,直到找到一点r使得它有x的出边,若没有这样的r,那么parent(np)=init,结束。否则,令q=trans(r,x),若Max(q)=Max(r)+1,则令parent(np)=q,结束;否则新建节点nq,nq的转移复制q的,parent(nq)=parent(q),parent(q)=parent(np)=nq,并且把r的祖先中出边x到q的,都将出边x连到nq上。

    我们给出AABBABD创建SAM的整个过程,

    首先,一开始只有一个头,它的长度为0



    接着插入第一个字母A


    接着插入第二个字母A

    接着插入第三个字母B

    接着插入第四个字母B



    接着插入第5个字母A



    接着插入第6个字母B



    最后插入D

  • 相关阅读:
    Catalyst揭秘 Day6 Physical plan解析
    Spark小课堂Week6 启动日志详解
    Spark小课堂Week5 Scala初探
    java并发再次积累
    java 2017/6/26杂记
    comparator接口与Comparable接口的区别
    ArrayList、Vector、HashMap、HashSet的默认初始容量、加载因子、扩容增量
    Java的快速失败和安全失败
    关于URL和http协议,http消息格式
    java中hashSet原理
  • 原文地址:https://www.cnblogs.com/jianglangcaijin/p/6035911.html
Copyright © 2020-2023  润新知