欢迎使用CSDN-markdown编辑器

后缀自动机


有穷自动机

有穷自动机(FA)可以表示成如下的形式

FA=(s0,S,N,Σ,P)

其中 s0 表示初始状态, S 表示状态集合,N表示终止状态集合,那么有 NS Σ 表示字母表,即读入的字符所属的集合, P 表示状态转移函数,即P:S,ΣS

这样,任意读入一个字符串,我们可以通过指定一台有穷自动机来判断读入的字符串是否符合我们的预期

后缀自动机

简述

后缀自动机是一台有穷自动机,用以识别一个字符串是否是给定字符串 a 的后缀,其具有以下的特点:

  • 自动机中从初始状态到其它非初始状态的一条路径,构成a的一个子串

    • 对于 a 的每一个子串sa,用 right(s) 表示字串 s 在原串a中的结束位置。例如 a=ababa s=aba ,那么 right(s)={3,5}
    • 对于两个不相同的子串 x y,如果 right(x)=right(y) ,那么我们可以认为在后缀自动机中, x y是等价的,因为从两个子串开始,会出现相同的原串后缀。也就是说,在自动机中我们可以用一个状态 p 来表示这两个子串,那么定义状态的right集合 right_S(p)={right(a)|as0p}
    • 对于两个 right 集合不相同的子串 x y,假设 r=right(x)right(y) ,那么可以看出, x y两个子串同时在 r 处结束,那么这两个子串一定有一个是另一个的后缀。不妨令x y 的后缀,那么一定有right(y)right(x),因此,两个 right 集合不相同的子串,要么一个是另一个的真子集,要么就完全不相交。
    • 现在回过头来考虑第二条特点,两个不相同的子串, right 集合完全相同,一定也存在后缀性质。因此可以得知,任意一个状态 p ,所能够代表的子串,长度一定存在一个区间当中,假设是[Min(p),Max(p)]。那么对于另一个状态 q ,如果right_S(q)right_S(p),且 Max(q)=Min(p)1 ,那么我们可以定义 q p的父亲状态,那么这种父亲状态的关系可以构成一棵树,我们称之为 parent 树,状态 q 所代表的子串称作p所代表的子串的真·后缀
    • 构造

      后缀自动机的构造,我们使用一种叫做增量法的方式,依次向自动机中插入一个字符,直到字符串中字符全部插入自动机中。
      假设我们现在添加到第 i 个字符a[i],令 last 表示添加第 i1 个字符之后所处的状态,令 np 表示添加字符 a[i] 新建的状态。

      首先,很显然,任意以 a[i1] 为结尾的子串,再加上一个字符 a[i] ,也是原串的子串,那么无条件地,所有以 a[i1] 为结尾的子串,都要向状态 np 有一条状态转移的边;换句话说,串 a[1..i1] 的所有后缀,都要向后再延伸一个字符 a[i]
      如果某一个状态 p ,它已经有一条向后延伸字符a[i]的边,假设这条边转移的状态是 q 。那么对于q来讲,现在它的 right 集合,要多一个结束位置为 i ,并且这个子串的长度为Max(p)+1。由于 q p经过一条边转移得到的,那么很显然有 Max(q)Max(p)+1 。现在考虑:

      • 如果 Max(q)=Max(p)+1 ,那么 q 状态现在代表的字符串没有任何需要变动,只是需要在right集合中添加一个元素而已,我们并不用改动。
      • 如果 Max(q)>Max(p)+1 ,那么 q 状态可以被分成两部分,一部分是长度为Max(p)+1的字符串,这部分字符串的 right 集合多了一个位置 i ,另一部分并不用改变。由于现在状态q所代表的字符串的 right 集合出现了分歧,现在考虑将状态 q 分成两部分,分别代表上述的两部分字符串,依然用q代表 [Max(p)+2,Max(q)] 部分,用新状态 nq 代表最长长度是 Max(p)+1 的部分,可以发现, nq 所代表的字符串一定是 q 代表的字符串的真后缀,并且是最近的真后缀,那么q的父亲就是 nq

      现在我们可以发现,其实状态 last ,代表的就是串 a[1..i1] (最长的情况下),那么找寻串 a[1..i1] 的所有后缀时,其实可以分为两部分,一部分在状态 last 里面包含着,另一部分就是状态 last 的真后缀,我们可以通过父亲关系来寻找它的真后缀。
      由于真后缀的 right 集合是逐渐扩大的,因此,如果某一个状态 p last的真后缀状态,并且已经具有了边 a[i] ,那么状态 p 的真后缀状态也具有了边a[i],并且都转移到同一个状态 q 。由于状态p last 的真后缀状态,那么两者同时添加一个字符 a[i] ,得到的状态也具有同样的真后缀形态,那么现在我们知道,状态 q 一定是状态np的真后缀状态。注意这里的 q 最大长度是Max(p)+1,也就是上文所述分裂出来的新状态。
      构造完成!

      实现

      
      inline void build_suffix_automation(char *str)  
      {  
          statepool[++cnt] = cur = root = new state(0);  
          for (char *s = str; *s; s++)  
          {  
              int x = *s - 'a';  
              state *p = cur;  
              cur = statepool[++cnt] = new state(s - str + 1);  
              for (; p && !p->trans[x]; p = p->fa) p->trans[x] = cur;  
              if (p == NULL)  
                  cur->fa = root;  
              else  
              {
                  state *q = p->trans[x];
                  if (q->len == p->len + 1)
                      cur->fa = q;
                  else
                  {
                      state *nq = new state(0);
                      statepool[++cnt] = nq;
                      nq->len = p->len + 1;
                      memcpy(nq->trans, q->trans, sizeof q->trans);
                      nq->fa = q->fa;
                      cur->fa = q->fa = nq;
                      for (; p && p->trans[x] == q; p = p->fa)
                          p->trans[x] = nq;
                  }
              }
          }
      }
      
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值