后缀树及其应用

Suffix Trie  又称后缀Trie或后缀树。它与Trie树的最大不同在于,后缀Trie的字符串集合是由指定字符串的后缀子串构成的。比如、完整字符串"minimize"的后缀子串组成的集合S分别如下:

         s1=minimize

         s2=inimize

         s3=nimize

         s4=imize

         s5=mize

         s6=ize

         s7=ze

         s8=e

      然后把这些子串的公共前缀作为内部结点构成一棵"minimize"的后缀树,如图所示,其中上图是Trie树的字符表示,下图是压缩表示(详细见《Trie树 》)。可见Suffic Trie是一种很适合操作字符串子串的数据结构。 它和PAT tree在这一点上类似。

 

Suffix Trie的创建 

      标准Tire树的每一个内部结点只有一个字符,也就是说公共前缀每一次只找一个。而Suffix Trie的公共前缀可以是多个字符,因此在创建Suffix Trie的时候,每插入一个后缀子串,就可能对内部结点造成一次分类。下面我们我们看一种后缀树构造算法。以"minimize"为例:

      当插入子串时,发现叶子结点中的关键字与子串有公共前缀,则需要将该叶子结点分裂。如上图第3到4步。否则,重新创建一个叶子结点来存放后缀,如上图第1到2步

Suffix Trie的子串查询

     如果在后缀树T中查找子串P,我们需要这样的过程:

     (1) 从根结点root出发,遍历所有的根的孩子结点:N1,N2,N3....

     (2) 如果所有孩子结点中的关键字的第一个字符都和P的第一个字符不匹配,则没有这个子串,查找结束。

     (3) 假如N3结点的关键字K3第一个字符与P的相同,则匹配K3和P。

          若 K3.length>=P.length  并且K3.subString(0,P.length-1)=P,则匹配成功,否则匹配失败。

          若 K3.length<=P.length  并且K3=P.subString(0, K3.length-1),则将子串P1=P.subString(K3.length, P.length); 即取出P中排除K3之后的子串。然后P1以N3为根结点继续重复(1)~(3)的步骤。直到匹配完P1的所有字符,则匹配成功。否则匹配失败。

      查询效率:很显然,在上面的算法中。匹配成功正好比较了P.length次字符。而定位结点的孩子指针,和Trie情况类似,假如字母表数量为d。则查询效率为O(d*m),实际上,d是固定常数,如果使用Hash表直接定位,则d=1.

      因此,后缀树查询子串P的时间复杂度为O(m),其中m为P的长度。

Suffix Trie的应用

      标准Trie树只适合前缀匹配和全字匹配,并不适合后缀和子串匹配。而后缀树在这方面则非常合适。 

      另外后缀树也可以进行前缀匹配。 如果模式串P是字符串S的前缀的话,那么从根结点出发遍历后缀树,一定能够寻找到一条路径完全匹配完P。比如上图: 模式串P=“mini”,主串S="minimize"。P从根节点出发,首先匹配到结点mi,然后再匹配孩子结点nimize。直到P中所有的字符都找到为止。所以P是S的前缀。

 

 

1,后缀树(Suffix tree)是一种数据结构,能快速解决很多关于字符串的问题。后缀树提出的目的是用来支持有效的字符串匹配和查询。 
简单点说,后缀树就是将一个给定字符串的所有后缀全部压入一个Trie,然后将只有单个叶子的节点压缩,从而形成的一棵树. 

2,后缀树的用途,总结起来大概有如下几种 
(1). 查找字符串o是否在字符串S中。 
方案:用S构造后缀树,按在trie中搜索字串的方法搜索o即可。 
原理:若o在S中,则o必然是S的某个后缀的前缀。 
例如S: leconte,查找o: con是否在S中,则o(con)必然是S(leconte)的后缀之一conte的前缀.有了这个前提,采用trie搜索的方法就不难理解了。 
(2). 指定字符串T在字符串S中的重复次数。 
方案:用S+’$'构造后缀树,搜索T节点下的叶节点数目即为重复次数 
原理:如果T在S中重复了两次,则S应有两个后缀以T为前缀,重复次数就自然统计出来了。 
(3). 字符串S中的最长重复子串 
方案:原理同2,具体做法就是找到最深的非叶节点。 
这个深是指从root所经历过的字符个数,最深非叶节点所经历的字符串起来就是最长重复子串。 
为什么要非叶节点呢?因为既然是要重复,当然叶节点个数要>=2。 
(4). 两个字符串S1,S2的最长公共部分 
方案:将S1#S2$作为字符串压入后缀树,找到最深的非叶节点,且该节点的叶节点既有#也有$(无#)。 

3,后缀树的构造:O(n) 
给定一个txt:`mississippi' 
(1)得到txt的所有后缀: 
T1  = mississippi = txt 
T2  = ississippi 
T3  = ssissippi 
T4  = sissippi 
T5  = issippi 
T6  = ssippi 
T7  = sippi 
T8  = ippi 
T9  = ppi 
T10 = pi 
T11 = i 
T12 =               (empty) 
(2)将所有非空后缀进行排序,得到: 
T11 = i 
T8  = ippi 
T5  = issippi 
T2  = ississippi 
T1  = mississippi 
T10 = pi 
T9  = ppi 
T7  = sippi 
T4  = sissippi 
T6  = ssippi 
T3  = ssissippi 
(3)将所有后缀的公有前缀进行合并,即可得到: 

           tree                       substrings 

tree-->|---mississippi                m .. mississippi 
       | 
       |---i-->|---ssi-->|---ssippi   i .. ississippi 
       |       |         | 
       |       |         |---ppi      issip,issipp,issippi 
       |       | 
       |       |---ppi                ip, ipp, ippi 
       | 
       |---s-->|---si-->|---ssippi    s .. ssissippi 
       |       |        | 
       |       |        |---ppi       ssip, ssipp, ssippi 
       |       | 
       |       |---i-->|---ssippi     si .. sissippi 
       |               | 
       |               |---ppi        sip, sipp, sippi 
       | 
       |---p-->|---pi                 p, pp, ppi 
               | 
               |---i                  p, pi 

--- Suffix Tree for "mississippi" --- 

4,应用 

问题: 
寻找字符串S的最长回文字符串. 
注:所谓“回文”是指当一个字符串正序读和逆序读时都一样。即p=reverse(p) 

分析: 
等价于寻找S与reverse(S)的最长公共部分.

posted on 2013-03-04 17:05  waxili  阅读(540)  评论(0编辑  收藏  举报

导航