后缀树的学习(二)

来源:互联网 发布:帝国cms 附件绑定域名 编辑:程序博客网 时间:2024/06/11 02:31

以下内容来自转载:

 上一篇文章已经介绍了后缀树的前前后后的知识,并且采用各种技巧逼近线性时间了,至于具体怎么操作大家看完之后应该多多少少有点想法了。而之所以将本文跟上一篇文章分开,主要考虑有三:

  • 第一,和在一起文章就会太长了,看的头疼。
  • 第二,理论跟实现本来就有差异,本文中一些具体实现并没有严格遵守上文中的条条框框。当然了,主题思想是一样的。
  • 第三,本文会从具体实现的角度,在实现过程中进一步阐述上文中的原理。换个角度看问题,会加深理解。

  声明一下,原本我打算自己操刀来写,但是我在网络上搜索到一篇很好的资源,本文也基本上沿着它的思路来写,建议英文不太差的小伙伴们自己先看看原文。当然喽,我也会把本文跟前一篇文章结合起来一起阐述,让大家体味到理论跟实现是如何水乳交融的。

     上文中提到我们最终只是需要沿着去除头尾之后的“尾部链表”以及根节点更新增加节点就好了。但是我们怎么来做呢?带着这个问题,正式进入我们的实现环节!

     首先引入两个概念:

    • 当前活跃点active point:这是一个三元组包含了三个信息(活跃点,活跃边,活跃半径)。初始值为(root,'\0x',0),代表活跃点为根节点,没有活跃边,活跃长度为0;
    • 计数器remainder: 记录我们在当前阶段需要插入的叶子节点数目。注意到我们每次插入都会增加新的叶子节点,具体原因见上一篇文章。初始值为1,每次进入新阶段增加1,每次从活跃点新增一个叶子节点减少1。

  我们每一阶段的目标都是把当前阶段的所有后缀插入到后缀树中。至于叶子节点,我们跟上文所说的一样,采用   [ , #]格式,每到一个新的阶段自动更新。

 

  好了,下面给出构建后缀树的三个定理:

  规则一(活跃点为根节点时候的插入):

    • 插入叶子之后,活跃节点依旧为根节点;
    • 活跃边更新为我们接下来要更新的后缀的首字母;
    • 活跃半径减1;

  规则二(后缀链表):

    • 每个阶段,当我们建立新的内部节点并且不是该阶段第一次建立内部节点的时候,我们需要用指针从当前内部节点指向本阶段最近一次建立的内部节点

规则三(活跃点不为根节点时候的插入):

    • 如果当前活跃点不是根节点,那么我们每次从活跃点新增一个叶子之后,就要沿着后缀链表到达新的点,并更新活跃节点;如果不存在后缀链表了,我们就转移到根节点,将活跃节点更新为根节点。活跃半径以及活跃边不变。

 

      是不是感觉这里面的“后缀链表”跟我们之前谈到的尾部链表非常相似啊,而且这个活跃点的概念是不是跟之前的尾部链表也有千丝万缕的关系呢,哈哈。留这些疑问!待会儿再说。我们先来通过一个例子来具体体验一把怎么建立后缀树。例子来自上文中提到的那篇原文。

  我们为字符串abcabxabcd建立后缀树吧。

  一开始活跃点三元组为(root,’\0’,0),计数器remainder为1;

 

第一阶段插入字符a,#=1。

  我们从当前活跃节点(根节点)出发看看有没有那个边的第 0(活跃半径) 个字母是a,发现没有,于是按照规则一插入叶子节点。活跃边更新为接下来要插入的后缀首字母,但是由于已经没有需要插入的了,故而活跃边也不变便;由于活跃半径已经是0,故而不用减1了;计数器remainder减少1变成0。我们获得了这样一棵树

clip_image001

  因为当前#=1,故而上图的这个后缀树就是下图(注意一下,区间[0,1]的含义是第一个字符,这点跟之前的表示方法不太一样)

clip_image002

  此时三元组为(root,’\0’,0),remainder为0;

 

第二阶段,插入字符b,#=2; 三元组不变仍为(root,’\0’,0),remainder增加1,等于1;

  我们从当前活跃节点(根节点)出发看看有没有哪个边的第 0(活跃半径) 个字母是b,发现没有,于是按照规则一插入叶子节点。情况同上,三元组为(root,’\0’,0),remainder为0;我们获得了这样一棵树:

clip_image003clip_image004

  因为#=2,故而左图等同于右图。

 

第三阶段,插入字符c;#=3;三元组不变仍为(root,’\0’,0),remainder增加1,等于1;

  跟第二阶段类似,三元组为(root,’\0’,0),remainder为0;我们获得了这样一棵树:

clip_image005clip_image006

  因为#=3,故而左图等同于右图。

 

第四阶段,插入字符a; #=4; 三元组为(root,’\0’,0),remainder增加1,等于1;

  这时,我们从当前活跃节点(根节点)出发看看有没有哪个边的第 0(活跃半径) 个字母是a。结果发现了!!!那么此时,我们更新三元组为(root, ‘a’, 1), remainder不变。更新完系数之后,我们就进入下一阶段了。

  为什么?还记得上一篇文章中,”当新后缀出现在原先后缀树中”是怎么办么?哈哈,就是更新系数后不管了。

  我们获得了这样一棵树:

clip_image005[1]clip_image007

  看出来了吧,树的形状没有变,但是叶子节点的边自动更新了。这也就是上一篇文章中提到了自动更新叶节点。按上文的说法这里应该有尾部链表从第一个点(边为abca)指向第二个点(边为bca),第二个点指向第三个点(边为ca),第三个点指向根节点,但是呢,显然这个链表上除了根节点都是叶节点,没有保存的必要啊。所以这里没有看到“尾部链表”的痕迹。这也是一种优化,但是,后面你会见到它的。哈哈,所谓神龙见首不见尾!我们接着来看。

 

第五阶段,插入字符b;#=5; 三元组为(root,’a’,1),remainder增加1,等于2;

      这时,我们从当前活跃点(根节点)出发看看边首字母为a(活跃边)的第1(活跃半径)个字母是b。结果又发现了!!

  注意一下,之前因为没有明确的活跃边,故而可以随便找边,但是确定了活跃边之后就只能沿着活跃边来找了。

  因为这一次我们需要插入的后缀除了b之外,还有上次剩余的,故而我们需要插入的后缀是ab ,b。活跃边不变,只是活跃半径需要增加1,等于2。于是三元组变成了(root,’a’,2),计数器remainder不变,仍然为2。

  我们获得了这样一棵树:

clip_image008

 

第六阶段,插入字符x; #=6; 三元组为(root,’a’,2), remainder增加1,等于3;我们需要插入的后缀有abx,bx,b

  这时,我们从当前活跃点(根节点)出发看看沿着活跃边(边首字母为a)的第2(活跃半径)个字母是不是x。结果没发现!

  于是我们按照规则一,从当前生长点(活跃点沿着活跃边走活跃半径个字符)增加一个叶子,插入x,也就是代表了abx。

     获得了这个树:

clip_image010

  插入完abx之后我们需要插入bx了,但是我们需要先调整三元组。活跃边首字母就应该变成b了,活跃半径减少1,于是我们的三元组变成了(root,’b’,1),remainder减少1,等于2。根据规则一,我们再从当前活跃点(根节点)出发看看沿着活跃边(边首字母为b)的第1(活跃半径)个字母是不是x。结果不是!于是再按照规则一,从当前生长点(活跃点沿着活跃边走活跃半径个字符)增加一个叶子,插入x,也就代表了bx。

  我们获得了这个树:

clip_image011

  但是还没完,因为这已经是我们在此阶段建立的第二个内部节点了,根据规则二,故而我们需要建立”后缀链表”。此时三元组变成(root,’x’,0), remainder减少1,等于1。

clip_image012

  接下来就要插入x了。此时活跃半径已经是0了,于是活跃边一定是空的了,我们查看根节点所有边中首字母有没有是x的,结果没有,于是插入x。从当前生长点(此时因为活跃半径是0,活跃点就是生长点)增加叶子,插入x。得到下面这颗树。根据规则一,此时的三元组变成了(root, ‘\0’, 0),remainder减少1,等于0。此阶段顺利结束了。

clip_image013

 

第七阶段,插入字符a;#=7;三元组为(root, ‘\0’, 0),remainder增加1,等于1。需要增加的后缀只有一个就是a;

      还是一样,鉴于活跃半径为0,我们从看看根节点有那个边的首字母为a,结果发现了。于是我们调整三元组为(root,’a’,1)。结束

 

第八阶段,插入字符b;#=8;三元组为(root, ‘a’, 1),remainder增加1,等于2。需要增加的后缀有两个ab,b;

     我们先从当前活跃节点(根节点)出发,沿着活跃边的第1(活跃半径)个字符是不是b,结果是!于是我们调整三元组为(root, ‘a’, 2)。

  注意了,我们已经来到了一个内部节点而不再跟以前一样停留在边上,于是我们再次调整三元组(类似于初始化)为(node1,’\0’,0)。这里,node1代表了上图中从根节点出发经过边ab,到达的那个点。

 

第九阶段,插入字符c;#=9;三元组为(node1,’\0’,0),remainder增加1,等于3。需要增加的后缀有三个abc,bc,c;

   鉴于活跃半径为0,我们直接看看当前活跃点(也就是node1)的边有没有以c为首字母的。结果有,于是更新三元组(node1,’c’,1)。注意一下虽然此时应该最先插入后缀abc,但是由于活跃点已经变化了,我们以当前活跃点为基准,故而活跃边以’c’标志。

 

第十阶段,插入字符s;;#=10;三元组为(node1,’c’,1),remainder增加1,等于4。需要增加的后缀有四个abcd,bcd,cd,d;

       我们从当前活跃点node1出发沿着活跃边c,第1个字符是否是d,发现不是!于是在当前生长点(活跃点沿着活跃边前进活跃半径个字符)增加一个叶子,得到下图的树。

clip_image014

  由于当前活跃点不是根节点,所以按照规则三,我们沿着后缀链表前进,更新三元组为(node2,’c’,1),remainder减少1,等于3。node2为下图中的红色点。

clip_image015

  从node2出发沿着活跃边c出发,第1个字符是不是d,发现不是,于是在当前生长点增加叶子。注意此时要按照规则二建立新的后缀链表了。

clip_image016

 

  按照规则三,沿着后缀链表,由于已经没有下一个节点了,于是跳回到根节点,更新三元组(root,’c’,1)。remainder减少1,等于2.

      从根节点出发沿着活跃边c,第1个字符是不是d?发现不是,于是在当前生长点增加叶子,注意仍按按照规则二建立后缀链表

clip_image018

  之后按照规则一更新三元组,活跃点不变,当前带插入的后缀是d,故而修改活跃边为d,活跃半径减少1变成0。remainder减少1,等于1。

  现在插入后缀d,鉴于活跃半径为0,看看根节点有没有边是以d为首字母的,发现没有!于是增加叶子。

clip_image019

  至此,全都结束了!后缀树建立完毕

 

  看完上面的建树过程,有点晕吧,哈哈,其实我也是。现在来说说上述算法跟上一篇文章提到的方法之间的关联吧。

  上一篇文章中我们提到的方法是每次沿着优化后的尾部链表更新,增加叶节点。其实我们这么来看,整个尾部链表是由三部分组成的,第一部分是叶子节点;第二部分是“除根节点外会增加叶子的节点”;第三部分是会导致 新后缀出现在原先后缀树中 的节点,这部分节点对树结构没有影响;所谓优化后的尾部链表是指去除第一部分和第三部分。当然喽第二部分第三部分都可能是空的。这样一来其实优化后的尾部链表由两部分组成:“尾部链表”的一部分(在本算法中就是后缀链表)。但是理论上这么说就可以了,实际上怎么来实现呢?

  我们看一下,因为”一朝为叶,终身为叶”,而每次遍历后缀链表的时候都会增加一个叶子,而最终的后缀树也只有|T|个叶子,所以我们一共需要遍历后缀链表|T|次。显然了,后缀链表是很“少”的。故而我们再去保留尾部链表,然后去定位第一个非叶子节点就很划不来了。于是,尾部链表作为一个理解上面的工具,在实践中(上述算法)就不维护啦,我们直接采用后缀链表。

  于是乎,我们回过头来看看上述算法。

  remainder是计数器,表明到当前阶段还需插入几个后缀,每个阶段增加1,是代表了后缀--从当前将要被插入的字符开始到原始串尾的这个后缀。当也叶子被插入的时候,rremainder减少1。

  首先初始化的时候活跃点是根节点,活跃半径是0。于是我们开始准备插入一个字符,如果字符没在根节点的任何一条边(默认为链接各个儿子的边)中出现,那么表明什么意思?表明当前生长点(根节点,所谓生长点就是准备长叶子的点),就是我们“尾部列表中”第二部分的节点,而remainder为1,代表当前只需插入一个叶子,于是插入即可,remainder减少1;如果字符在根节点的某一条边中出现,那么这是什么意思?这表明当前生长点(根节点),是我们第三部分的点,于是我们调整一下当前生长点的位置,这是为什么呢?因为,假设当前要插入的字符是a,下一次要插入b,那么我们这一阶段没插入叶子,下一阶段多了一个“欠债“,就要还要插入ab。那么我们将生长点挪到树中字符a的后面,就意味着我们下一阶段只需要在生长点后面插入字符b就可以了,一步到位,方便快捷。鉴于本阶段没有插叶子,所以remainder不变。调整三元组也是为了”挪动生长点“。

  下一次,我们要插入字符b的时候,我们在当前生长点后面找一找有没有b(其实就是为了验证当前生长点是不是后缀链表中的第三部分的点),如果没有,意味着是第二部分的点(因为肯定不是叶节点,又不是第三部分的点),那么就插入叶节点吧,remainder减少1,调整活跃边为下一个需要插入后缀的首字母,也就是b,活跃半径减少1。这也是为了调整生长点(快速找到下一个插入叶子的点)。于是看看根节点哪个边首字母为b,如果没有就插入新节点,如果有就调整生长点(这一块跟前面类似,不再多说)。我们说说另一种情况,就是我们在生长点(root,’a’,1)之后还找到了b,这说明当前生长点是第三部分的点,那么怎么办呢,继续调整生长点为(root,’a’,2)。

  假设上一次之后,调整生长点为(root,’a’,2),这一次再插入字符c,remainder增加1,等于3。如果当前生长点后没有c,意味着当前生长点是第二部分的点,于是插入叶子c就好了,代表着”欠债“后缀abc。remaidner减少1,调整生长点为(root,’b’,1),为什么如此确定根节点有某条边是以b为首字母呢?原因是这样的,你看啊,我们活跃半径是2,必然是因为我们刚刚插入的那个后缀abc的前两个字符出现在后缀树中,既然ab出现在了后缀树中,那么依据前文中的原理,b也一定出现在后缀树中。大家发现没有,这个调整生长点的过程是不是太迅速啦。

  事实上来说我们就凭借上述的这个过程似乎已经建立后缀树了,但是我们仔细看看,如果定位生长点的时候的这个(root,’a’,2)中的活跃半径不是2,而是一个很大的数字,比如100,那么就算我们知道下一个生长点是(root,’b’,99),真的能定位到准确的生长点么??不一定吧,根节点沿着一条特定的边走99步,可不一定只能到一个点啊,哈哈!

  这就是问题所在,所以我们调整生长点为(root,’a’,2)的时候,如果从当前活跃点开始,沿着活跃边走了活跃半径个距离,我们停在活跃边上面一个隐式节点那就没事,要是停到了内部节点node1,那我们就要更新活跃点为这个内部节点啦!然后调整三元组为(node1,’\0’,0)了。这里假设我们将(root,’a’,2)更新为(node1,’\0’,0)。我们再来看一下,假设下一个字符是c,且node1后面有一个边是以c为首字母的,于是更新三元组为(node1,’c’,1);假设我们之后要插入字符x,而从node1开始没有那个边是以x为首字母的,这时候就要插入叶子x了,代表了后缀abcx,我们下一个就要插入后缀bx了,但是怎么定位到下一个生长点?跟前面一样更新生长点为(node1,’b’,0)?

  肯定不行啊,因为下一个生长点根本就不在node1的子树里面。为什么?因为node1的子树里面任何一个点代表的字符串都是以ab(也就是根节点到node1那个边),而我们插入的是bcx。那么下一个生长点是什么呢?我们猜测一下啊,应该是(node2,’c’,1)其中根节点到node2的路径上面的字符是b!这样就可以啦。首先我们来明确一下存不存在这样一个结果node2的生长点?是存在的,因为既然后缀树中有ab这个内部节点(注意我的用词,ab已经成为i类不节点了,而不是刚刚建立的),那么必然有b这个内部节点(因为假设node1后面的两个儿子分别为x,y,那么后缀树中存在abx,和aby那么也必然存在bx,by所以也必然存在node2);同时后缀树中有字符串abc,那么必然也有串bc,于是证明是有的!下一个问题是,我们怎么快速将生长点从(node1,’c’,1)定位到(node2,’c’,1),所以我们想要是有一条指针从node1指向node2就好了,所以我们应该之前建立这个后缀链表这样下次就方便了。

  什么时候建立呢?我们来看啊,当初我们建立ab这个内部节点的时候,内部节点或者是已经存在了或者下一步就存在(注意跟上面的对比啊,这次是说我们当初建立ab内部节点的时候,所以这个内部节点是刚建立的,故而内部节点b暂时还不一定存在)为什么说下一步就存在呢?(因为: 如果 aS 停在一个内部节点上面,也就是 aS 后有分支,那么当前的 T[0 .. i-1] 肯定有子串 aS+c 以及aS+d ( c 和 d 是不同的两个字符) 这两个不同的子串,于是肯定也有 S+c 以及 S+d 两个子串了。至于“下次扩展时产生”的这种情况,则发生在已经有 aS+c 、 S+c ,刚加入 aS+d (于是产生了新的内部节点),正要加入 S+d 的时候。)下面我们来看啊,既然"下一步"内部节点b一定存在了,那就在"下一步"的时候建立指针从内部节点ab指向内部节点b。

  当我们走到后缀链表的尾部时候,意味着我们已经处理完毕remainder的前两个(就是从根节点到node1的边长,这两个后缀意味着abcx,bcx)”欠债”。于是我们回到根节点,当然了,活跃边和活跃半径不变。那么为什么此时从根节点出发沿着活跃边走活跃半径个字符就能唯一锁定生长点呢?

  答案是这样的,因为啊,我们之前将活跃点更新为node1之后可能还是会更新活跃点,我们将最后更新的活跃点命名为nodeN,此时的三元组为(nodeN,active_edge,active_length),我们知道这个三元组确定的生长点位于nodeN的某条边上的一个隐式节点。我们知道我们路过nodeN这个活跃点的时候,nodeN那边已经存在后缀链接了,所以当我们沿着后缀链接插入叶子,在最终回到根节点时候三元组变为nodeN(root,active_edge,active_length)。一步定位!而且在沿着后缀链表定位生长点的时候也是一步定位!

 

     下面说明一下为什么上述算法是线性的。首先啊,我们来看上面一共涉及到了几个操作:插入叶节点(每次插入消耗常数时间,总叶节点|T|个,故而总时间为O(|T|),建立后缀链表(每次建立一个指针只需要沿着前面建好的后缀链表走一下,然后一步定位,所以每次建立一个指针只需要常数时间;而每次我们沿着后缀链表走都会插入新叶子,故而后缀链表一共也只有|T|这么长,故而一共需要插入O(|T|)次),更新三元组(因为涉及到活跃点的移动,故而我们这么看,没有更新活跃点的时候是常数时间,而更新活跃点的情况有点复杂,我们这么理解:每次更新活跃点都会导致活跃半径相应减小,而活跃半径始在整个建树的过程中最多增加|T|,故而整个更新活跃点所需时间为O(|T|)),其他诸如remainder的调整等等,每次花费常数时间,总和为O(|T|)。综上所述,线性算法降临了!!!!

 

代码实现(参考版):

C#:

复制代码
  1 using System;  2 using System.Collections.Generic;  3 using System.IO;  4 using System.Linq;  5 using System.Text;  6    7 namespace SuffixTreeAlgorithm  8 {  9     public class SuffixTree 10     { 11         public char? CanonizationChar { get; set; } 12         public string Word { get; private set; } 13         private int CurrentSuffixStartIndex { get; set; } 14         private int CurrentSuffixEndIndex { get; set; } 15         private Node LastCreatedNodeInCurrentIteration { get; set; } 16         private int UnresolvedSuffixes { get; set; } 17         public Node RootNode { get; private set; } 18         private Node ActiveNode { get; set; } 19         private Edge ActiveEdge { get; set; } 20         private int DistanceIntoActiveEdge { get; set; } 21         private char LastCharacterOfCurrentSuffix { get; set; } 22         private int NextNodeNumber { get; set; } 23         private int NextEdgeNumber { get; set; } 24   25         public SuffixTree(string word) 26         { 27             Word = word; 28             RootNode = new Node(this); 29             ActiveNode = RootNode; 30         } 31   32         public event Action<SuffixTree> Changed; 33         private void TriggerChanged() 34         { 35             var handler = Changed; 36             if(handler != null) 37                 handler(this); 38         } 39   40         public event Action<string, object[]> Message; 41         private void SendMessage(string format, params object[] args) 42         { 43             var handler = Message; 44             if(handler != null) 45                 handler(format, args); 46         } 47   48         public static SuffixTree Create(string word, char canonizationChar = '$') 49         { 50             var tree = new SuffixTree(word); 51             tree.Build(canonizationChar); 52             return tree; 53         } 54   55         public void Build(char canonizationChar) 56         { 57             var n = Word.IndexOf(Word[Word.Length - 1]); 58             var mustCanonize = n < Word.Length - 1; 59             if(mustCanonize) 60             { 61                 CanonizationChar = canonizationChar; 62                 Word = string.Concat(Word, canonizationChar); 63             } 64   65             for(CurrentSuffixEndIndex = 0; CurrentSuffixEndIndex < Word.Length; CurrentSuffixEndIndex++) 66             { 67                 SendMessage("=== ITERATION {0} ===", CurrentSuffixEndIndex); 68                 LastCreatedNodeInCurrentIteration = null; 69                 LastCharacterOfCurrentSuffix = Word[CurrentSuffixEndIndex]; 70   71                 for(CurrentSuffixStartIndex = CurrentSuffixEndIndex - UnresolvedSuffixes; CurrentSuffixStartIndex <= CurrentSuffixEndIndex; CurrentSuffixStartIndex++) 72                 { 73                     var wasImplicitlyAdded = !AddNextSuffix(); 74                     if(wasImplicitlyAdded) 75                     { 76                         UnresolvedSuffixes++; 77                         break; 78                     } 79                     if(UnresolvedSuffixes > 0) 80                         UnresolvedSuffixes--; 81                 } 82             } 83         } 84   85         private bool AddNextSuffix() 86         { 87             var suffix = string.Concat(Word.Substring(CurrentSuffixStartIndex, CurrentSuffixEndIndex - CurrentSuffixStartIndex), "{", Word[CurrentSuffixEndIndex], "}"); 88             SendMessage("The next suffix of '{0}' to add is '{1}' at indices {2},{3}", Word, suffix, CurrentSuffixStartIndex, CurrentSuffixEndIndex); 89             SendMessage(" => ActiveNode:             {0}", ActiveNode); 90             SendMessage(" => ActiveEdge:             {0}", ActiveEdge == null ? "none" : ActiveEdge.ToString()); 91             SendMessage(" => DistanceIntoActiveEdge: {0}", DistanceIntoActiveEdge); 92             SendMessage(" => UnresolvedSuffixes:     {0}", UnresolvedSuffixes); 93             if(ActiveEdge != null && DistanceIntoActiveEdge >= ActiveEdge.Length) 94                 throw new Exception("BOUNDARY EXCEEDED"); 95   96             if(ActiveEdge != null) 97                 return AddCurrentSuffixToActiveEdge(); 98   99             if(GetExistingEdgeAndSetAsActive())100                 return false;101  102             ActiveNode.AddNewEdge();103             TriggerChanged();104  105             UpdateActivePointAfterAddingNewEdge();106             return true;107         }108  109         private bool GetExistingEdgeAndSetAsActive()110         {111             Edge edge;112             if(ActiveNode.Edges.TryGetValue(LastCharacterOfCurrentSuffix, out edge))113             {114                 SendMessage("Existing edge for {0} starting with '{1}' found. Values adjusted to:", ActiveNode, LastCharacterOfCurrentSuffix);115                 ActiveEdge = edge;116                 DistanceIntoActiveEdge = 1;117                 TriggerChanged();118  119                 NormalizeActivePointIfNowAtOrBeyondEdgeBoundary(ActiveEdge.StartIndex);120                 SendMessage(" => ActiveEdge is now: {0}", ActiveEdge);121                 SendMessage(" => DistanceIntoActiveEdge is now: {0}", DistanceIntoActiveEdge);122                 SendMessage(" => UnresolvedSuffixes is now: {0}", UnresolvedSuffixes);123  124                 return true;125             }126             SendMessage("Existing edge for {0} starting with '{1}' not found", ActiveNode, LastCharacterOfCurrentSuffix);127             return false;128         }129  130         private bool AddCurrentSuffixToActiveEdge()131         {132             var nextCharacterOnEdge = Word[ActiveEdge.StartIndex + DistanceIntoActiveEdge];133             if(nextCharacterOnEdge == LastCharacterOfCurrentSuffix)134             {135                 SendMessage("The next character on the current edge is '{0}' (suffix added implicitly)", LastCharacterOfCurrentSuffix);136                 DistanceIntoActiveEdge++;137                 TriggerChanged();138  139                 SendMessage(" => DistanceIntoActiveEdge is now: {0}", DistanceIntoActiveEdge);140                 NormalizeActivePointIfNowAtOrBeyondEdgeBoundary(ActiveEdge.StartIndex);141  142                 return false;143             }144  145             SplitActiveEdge();146             ActiveEdge.Tail.AddNewEdge();147             TriggerChanged();148  149             UpdateActivePointAfterAddingNewEdge();150  151             return true;152         }153  154         private void UpdateActivePointAfterAddingNewEdge()155         {156             if(ReferenceEquals(ActiveNode, RootNode))157             {158                 if(DistanceIntoActiveEdge > 0)159                 {160                     SendMessage("New edge has been added and the active node is root. The active edge will now be updated.");161                     DistanceIntoActiveEdge--;162                     SendMessage(" => DistanceIntoActiveEdge decremented to: {0}", DistanceIntoActiveEdge);163                     ActiveEdge = DistanceIntoActiveEdge == 0 ? null : ActiveNode.Edges[Word[CurrentSuffixStartIndex + 1]];164                     SendMessage(" => ActiveEdge is now: {0}", ActiveEdge);165                     TriggerChanged();166  167                     NormalizeActivePointIfNowAtOrBeyondEdgeBoundary(CurrentSuffixStartIndex + 1);168                 }169             }170             else171                 UpdateActivePointToLinkedNodeOrRoot();172         }173  174         private void NormalizeActivePointIfNowAtOrBeyondEdgeBoundary(int firstIndexOfOriginalActiveEdge)175         {176             var walkDistance = 0;177             while(ActiveEdge != null && DistanceIntoActiveEdge >= ActiveEdge.Length)178             {179                 SendMessage("Active point is at or beyond edge boundary and will be moved until it falls inside an edge boundary");180                 DistanceIntoActiveEdge -= ActiveEdge.Length;181                 ActiveNode = ActiveEdge.Tail ?? RootNode;182                 if(DistanceIntoActiveEdge == 0)183                     ActiveEdge = null;184                 else185                 {186                     walkDistance += ActiveEdge.Length;187                     var c = Word[firstIndexOfOriginalActiveEdge + walkDistance];188                     ActiveEdge = ActiveNode.Edges[c];189                 }190                 TriggerChanged();191             }192         }193  194         private void SplitActiveEdge()195         {196             ActiveEdge = ActiveEdge.SplitAtIndex(ActiveEdge.StartIndex + DistanceIntoActiveEdge);197             SendMessage(" => ActiveEdge is now: {0}", ActiveEdge);198             TriggerChanged();199             if(LastCreatedNodeInCurrentIteration != null)200             {201                 LastCreatedNodeInCurrentIteration.LinkedNode = ActiveEdge.Tail;202                 SendMessage(" => Connected {0} to {1}", LastCreatedNodeInCurrentIteration, ActiveEdge.Tail);203                 TriggerChanged();204             }205             LastCreatedNodeInCurrentIteration = ActiveEdge.Tail;206         }207  208         private void UpdateActivePointToLinkedNodeOrRoot()209         {210             SendMessage("The linked node for active node {0} is {1}", ActiveNode, ActiveNode.LinkedNode == null ? "[null]" : ActiveNode.LinkedNode.ToString());211             if(ActiveNode.LinkedNode != null)212             {213                 ActiveNode = ActiveNode.LinkedNode;214                 SendMessage(" => ActiveNode is now: {0}", ActiveNode);215             }216             else217             {218                 ActiveNode = RootNode;219                 SendMessage(" => ActiveNode is now ROOT", ActiveNode);220             }221             TriggerChanged();222  223             if(ActiveEdge != null)224             {225                 var firstIndexOfOriginalActiveEdge = ActiveEdge.StartIndex;226                 ActiveEdge = ActiveNode.Edges[Word[ActiveEdge.StartIndex]];227                 TriggerChanged();228                 NormalizeActivePointIfNowAtOrBeyondEdgeBoundary(firstIndexOfOriginalActiveEdge);229             }230         }231  232         public string RenderTree()233         {234             var writer = new StringWriter();235             RootNode.RenderTree(writer, "");236             return writer.ToString();237         }238  239         public string WriteDotGraph()240         {241             var sb = new StringBuilder();242             sb.AppendLine("digraph {");243             sb.AppendLine("rankdir = LR;");244             sb.AppendLine("edge [arrowsize=0.5,fontsize=11];");245             for(var i = 0; i < NextNodeNumber; i++)246                 sb.AppendFormat("node{0} [label=\"{0}\",style=filled,fillcolor={1},shape=circle,width=.1,height=.1,fontsize=11,margin=0.01];",247                     i, ActiveNode.NodeNumber == i ? "cyan" : "lightgrey").AppendLine();248             RootNode.WriteDotGraph(sb);249             sb.AppendLine("}");250             return sb.ToString();251         }252  253         public HashSet<string> ExtractAllSubstrings()254         {255             var set = new HashSet<string>();256             ExtractAllSubstrings("", set, RootNode);257             return set;258         }259  260         private void ExtractAllSubstrings(string str, HashSet<string> set, Node node)261         {262             foreach(var edge in node.Edges.Values)263             {264                 var edgeStr = edge.StringWithoutCanonizationChar;265                 var edgeLength = !edge.EndIndex.HasValue && CanonizationChar.HasValue ? edge.Length - 1 : edge.Length; // assume tailing canonization char266                 for(var length = 1; length <= edgeLength; length++)267                     set.Add(string.Concat(str, edgeStr.Substring(0, length)));268                 if(edge.Tail != null)269                     ExtractAllSubstrings(string.Concat(str, edge.StringWithoutCanonizationChar), set, edge.Tail);270             }271         }272  273         public List<string> ExtractSubstringsForIndexing(int? maxLength = null)274         {275             var list = new List<string>();276             ExtractSubstringsForIndexing("", list, maxLength ?? Word.Length, RootNode);277             return list;278         }279  280         private void ExtractSubstringsForIndexing(string str, List<string> list, int len, Node node)281         {282             foreach(var edge in node.Edges.Values)283             {284                 var newstr = string.Concat(str, Word.Substring(edge.StartIndex, Math.Min(len, edge.Length)));285                 if(len > edge.Length && edge.Tail != null)286                     ExtractSubstringsForIndexing(newstr, list, len - edge.Length, edge.Tail);287                 else288                     list.Add(newstr);289             }290         }291  292         public class Edge293         {294             private readonly SuffixTree _tree;295  296             public Edge(SuffixTree tree, Node head)297             {298                 _tree = tree;299                 Head = head;300                 StartIndex = tree.CurrentSuffixEndIndex;301                 EdgeNumber = _tree.NextEdgeNumber++;302             }303  304             public Node Head { get; private set; }305             public Node Tail { get; private set; }306             public int StartIndex { get; private set; }307             public int? EndIndex { get; set; }308             public int EdgeNumber { get; private set; }309             public int Length { get { return (EndIndex ?? _tree.Word.Length - 1) - StartIndex + 1; } }310  311             public Edge SplitAtIndex(int index)312             {313                 _tree.SendMessage("Splitting edge {0} at index {1} ('{2}')", this, index, _tree.Word[index]);314                 var newEdge = new Edge(_tree, Head);315                 var newNode = new Node(_tree);316                 newEdge.Tail = newNode;317                 newEdge.StartIndex = StartIndex;318                 newEdge.EndIndex = index - 1;319                 Head = newNode;320                 StartIndex = index;321                 newNode.Edges.Add(_tree.Word[StartIndex], this);322                 newEdge.Head.Edges[_tree.Word[newEdge.StartIndex]] = newEdge;323                 _tree.SendMessage(" => Hierarchy is now: {0} --> {1} --> {2} --> {3}", newEdge.Head, newEdge, newNode, this);324                 return newEdge;325             }326  327             public override string ToString()328             {329                 return string.Concat(_tree.Word.Substring(StartIndex, (EndIndex ?? _tree.CurrentSuffixEndIndex) - StartIndex + 1), "(",330                     StartIndex, ",", EndIndex.HasValue ? EndIndex.ToString() : "#", ")");331             }332  333             public string StringWithoutCanonizationChar334             {335                 get { return _tree.Word.Substring(StartIndex, (EndIndex ?? _tree.CurrentSuffixEndIndex - (_tree.CanonizationChar.HasValue ? 1 : 0)) - StartIndex + 1); }336             }337  338             public string String339             {340                 get { return _tree.Word.Substring(StartIndex, (EndIndex ?? _tree.CurrentSuffixEndIndex) - StartIndex + 1); }341             }342  343             public void RenderTree(TextWriter writer, string prefix, int maxEdgeLength)344             {345                 var strEdge = _tree.Word.Substring(StartIndex, (EndIndex ?? _tree.CurrentSuffixEndIndex) - StartIndex + 1);346                 writer.Write(strEdge);347                 if(Tail == null)348                     writer.WriteLine();349                 else350                 {351                     var line = new string(RenderChars.HorizontalLine, maxEdgeLength - strEdge.Length + 1);352                     writer.Write(line);353                     Tail.RenderTree(writer, string.Concat(prefix, new string(' ', strEdge.Length + line.Length)));354                 }355             }356  357             public void WriteDotGraph(StringBuilder sb)358             {359                 if(Tail == null)360                     sb.AppendFormat("leaf{0} [label=\"\",shape=point]", EdgeNumber).AppendLine();361                 string label, weight, color;362                 if(_tree.ActiveEdge != null && ReferenceEquals(this, _tree.ActiveEdge))363                 {364                     if(_tree.ActiveEdge.Length == 0)365                         label = "";366                     else if(_tree.DistanceIntoActiveEdge > Length)367                         label = "<<FONT COLOR=\"red\" SIZE=\"11\"><B>" + String + "</B> (" + _tree.DistanceIntoActiveEdge + ")</FONT>>";368                     else if(_tree.DistanceIntoActiveEdge == Length)369                         label = "<<FONT COLOR=\"red\" SIZE=\"11\">" + String + "</FONT>>";370                     else if(_tree.DistanceIntoActiveEdge > 0)371                         label = "<<TABLE BORDER=\"0\" CELLPADDING=\"0\" CELLSPACING=\"0\"><TR><TD><FONT COLOR=\"blue\"><B>" + String.Substring(0, _tree.DistanceIntoActiveEdge) + "</B></FONT></TD><TD COLOR=\"black\">" + String.Substring(_tree.DistanceIntoActiveEdge) + "</TD></TR></TABLE>>";372                     else373                         label = "\"" + String + "\"";374                     color = "blue";375                     weight = "5";376                 }377                 else378                 {379                     label = "\"" + String + "\"";380                     color = "black";381                     weight = "3";382                 }383                 var tail = Tail == null ? "leaf" + EdgeNumber : "node" + Tail.NodeNumber;384                 sb.AppendFormat("node{0} -> {1} [label={2},weight={3},color={4},size=11]", Head.NodeNumber, tail, label, weight, color).AppendLine();385                 if(Tail != null)386                     Tail.WriteDotGraph(sb);387             }388         }389  390         public class Node391         {392             private readonly SuffixTree _tree;393  394             public Node(SuffixTree tree)395             {396                 _tree = tree;397                 Edges = new Dictionary<char, Edge>();398                 NodeNumber = _tree.NextNodeNumber++;399             }400  401             public Dictionary<char, Edge> Edges { get; private set; }402             public Node LinkedNode { get; set; }403             public int NodeNumber { get; private set; }404  405             public void AddNewEdge()406             {407                 _tree.SendMessage("Adding new edge to {0}", this);408                 var edge = new Edge(_tree, this);409                 Edges.Add(_tree.Word[_tree.CurrentSuffixEndIndex], edge);410                 _tree.SendMessage(" => {0} --> {1}", this, edge);411             }412  413             public void RenderTree(TextWriter writer, string prefix)414             {415                 var strNode = string.Concat("(", NodeNumber.ToString(new string('0', _tree.NextNodeNumber.ToString().Length)), ")");416                 writer.Write(strNode);417                 var edges = Edges.Select(kvp => kvp.Value).OrderBy(e => _tree.Word[e.StartIndex]).ToArray();418                 if(edges.Any())419                 {420                     var prefixWithNodePadding = prefix + new string(' ', strNode.Length);421                     var maxEdgeLength = edges.Max(e => (e.EndIndex ?? _tree.CurrentSuffixEndIndex) - e.StartIndex + 1);422                     for(var i = 0; i < edges.Length; i++)423                     {424                         char connector, extender = ' ';425                         if(i == 0)426                         {427                             if(edges.Length > 1)428                             {429                                 connector = RenderChars.TJunctionDown;430                                 extender = RenderChars.VerticalLine;431                             }432                             else433                                 connector = RenderChars.HorizontalLine;434                         }435                         else436                         {437                             writer.Write(prefixWithNodePadding);438                             if(i == edges.Length - 1)439                                 connector = RenderChars.CornerRight;440                             else441                             {442                                 connector = RenderChars.TJunctionRight;443                                 extender = RenderChars.VerticalLine;444                             }445                         }446                         writer.Write(string.Concat(connector, RenderChars.HorizontalLine));447                         var newPrefix = string.Concat(prefixWithNodePadding, extender, ' ');448                         edges[i].RenderTree(writer, newPrefix, maxEdgeLength);449                     }450                 }451             }452  453             public override string ToString()454             {455                 return string.Concat("node #", NodeNumber);456             }457  458             public void WriteDotGraph(StringBuilder sb)459             {460                 if(LinkedNode != null)461                     sb.AppendFormat("node{0} -> node{1} [label=\"\",weight=.01,style=dotted]", NodeNumber, LinkedNode.NodeNumber).AppendLine();462                 foreach(var edge in Edges.Values)463                     edge.WriteDotGraph(sb);464             }465         }466  467         public static class RenderChars468         {469             public const char TJunctionDown = '';470             public const char HorizontalLine = '';471             public const char VerticalLine = '';472             public const char TJunctionRight = '';473             public const char CornerRight = '';474         }475     }476 }
复制代码

参考:https://gist.github.com/axefrog/2373868

java:

复制代码
  1 import java.io.*;  2 import java.util.*;  3 public class ST {  4     BufferedReader in;  5     PrintWriter out;  6   7     class SuffixTree {  8         final int oo = Integer.MAX_VALUE/2;  9         Node [] nodes; 10         char [] text; 11         int root, position = -1, 12                 currentNode, 13                 needSuffixLink, 14                 remainder; 15  16         int active_node, active_length, active_edge; 17  18         class Node { 19  20             /* 21                There is no need to create an "Edge" class. 22                Information about the edge is stored right in the node. 23                [start; end) interval specifies the edge, 24                by which the node is connected to its parent node. 25             */ 26  27             int start, end = oo, link; 28             public TreeMap<Character, Integer> next = new TreeMap<Character, Integer>(); 29  30             public Node(int start, int end) { 31                 this.start = start; 32                 this.end = end; 33             } 34  35             public int edgeLength() { 36                 return Math.min(end, position + 1) - start; 37             } 38         } 39  40         public SuffixTree(int length) { 41             nodes = new Node[2* length + 2]; 42             text = new char[length]; 43             root = active_node = newNode(-1, -1); 44         } 45  46         private void addSuffixLink(int node) { 47             if (needSuffixLink > 0) 48                 nodes[needSuffixLink].link = node; 49             needSuffixLink = node; 50         } 51  52         char active_edge() { 53             return text[active_edge]; 54         } 55  56         boolean walkDown(int next) { 57             if (active_length >= nodes[next].edgeLength()) { 58                 active_edge += nodes[next].edgeLength(); 59                 active_length -= nodes[next].edgeLength(); 60                 active_node = next; 61                 return true; 62             } 63             return false; 64         } 65  66         int newNode(int start, int end) { 67             nodes[++currentNode] = new Node(start, end); 68             return currentNode; 69         } 70  71         public void addChar(char c) throws Exception { 72             text[++position] = c; 73             needSuffixLink = -1; 74             remainder++; 75             while(remainder > 0) { 76                 if (active_length == 0) active_edge = position; 77                 if (!nodes[active_node].next.containsKey(active_edge())){ 78                     int leaf = newNode(position, oo); 79                     nodes[active_node].next.put(active_edge(), leaf); 80                     addSuffixLink(active_node); //rule 2 81                 } else { 82                     int next = nodes[active_node].next.get(active_edge()); 83                     if (walkDown(next)) continue;   //observation 2 84                     if (text[nodes[next].start + active_length] == c) { //observation 1 85                         active_length++; 86                         addSuffixLink(active_node); // observation 3 87                         break; 88                     } 89                     int split = newNode(nodes[next].start, nodes[next].start + active_length); 90                     nodes[active_node].next.put(active_edge(), split); 91                     int leaf = newNode(position, oo); 92                     nodes[split].next.put(c, leaf); 93                     nodes[next].start += active_length; 94                     nodes[split].next.put(text[nodes[next].start], next); 95                     addSuffixLink(split); //rule 2 96                 } 97                 remainder--; 98  99                 if (active_node == root && active_length > 0) {  //rule 1100                     active_length--;101                     active_edge = position - remainder + 1;102                 } else103                     active_node = nodes[active_node].link > 0 ? nodes[active_node].link : root; //rule 3104             }105         }106 107         /*108             printing the Suffix Tree in a format understandable by graphviz. The output is written into109             st.dot file. In order to see the suffix tree as a PNG image, run the following command:110             dot -Tpng -O st.dot111          */112 113         String edgeString(int node) {114             return new String(Arrays.copyOfRange(text, nodes[node].start, Math.min(position + 1, nodes[node].end)));115         }116 117         void printTree() {118             out.println("digraph {");119             out.println("\trankdir = LR;");120             out.println("\tedge [arrowsize=0.4,fontsize=10]");121             out.println("\tnode1 [label=\"\",style=filled,fillcolor=lightgrey,shape=circle,width=.1,height=.1];");122             out.println("//------leaves------");123             printLeaves(root);124             out.println("//------internal nodes------");125             printInternalNodes(root);126             out.println("//------edges------");127             printEdges(root);128             out.println("//------suffix links------");129             printSLinks(root);130             out.println("}");131         }132 133         void printLeaves(int x) {134             if (nodes[x].next.size() == 0)135                 out.println("\tnode"+x+" [label=\"\",shape=point]");136             else {137                 for (int child : nodes[x].next.values())138                     printLeaves(child);139             }140         }141 142         void printInternalNodes(int x) {143             if (x != root && nodes[x].next.size() > 0)144                 out.println("\tnode"+x+" [label=\"\",style=filled,fillcolor=lightgrey,shape=circle,width=.07,height=.07]");145 146             for (int child : nodes[x].next.values())147                 printInternalNodes(child);148         }149 150         void printEdges(int x) {151             for (int child : nodes[x].next.values()) {152                 out.println("\tnode"+x+" -> node"+child+" [label=\""+edgeString(child)+"\",weight=3]");153                 printEdges(child);154             }155         }156 157         void printSLinks(int x) {158             if (nodes[x].link > 0)159                 out.println("\tnode"+x+" -> node"+nodes[x].link+" [label=\"\",weight=1,style=dotted]");160             for (int child : nodes[x].next.values())161                 printSLinks(child);162         }163     }164 165     public ST() throws Exception {166         in = new BufferedReader(new InputStreamReader(System.in));167         out = new PrintWriter(new FileWriter("st.dot"));168         String line = in.readLine();169         SuffixTree st = new SuffixTree(line.length());170         for (int i = 0; i < line.length(); i++)171             st.addChar(line.charAt(i));172         st.printTree();173         out.close();174     }175 176     public static void main(String ... args) throws Exception{177         new ST();178     }179 }
复制代码

参考:http://stackoverflow.com/questions/9452701/ukkonens-suffix-tree-algorithm-in-plain-english

C语言:

复制代码
  1 #include <iostream>  2 #include <stdio.h>  3 #include <string>  4   5 const int oo = 1<<25;  6 const int ALPHABET_SIZE = 256;  7 const int MAXN = 5000;  8   9 using namespace std; 10  11 int root, last_added, pos, needSL, remainder, 12     active_node, active_e, active_len; 13  14 struct node { 15 /* 16    There is no need to create an "Edge" struct. 17    Information about the edge is stored right in the node. 18    [start; end) interval specifies the edge, 19    by which the node is connected to its parent node. 20 */ 21  22     int start, end, slink; 23     int next[ALPHABET_SIZE];     24  25     int edge_length() { 26         return min(end, pos + 1) - start; 27     } 28 }; 29  30 node tree[2*MAXN]; 31 char text[MAXN]; 32  33 int new_node(int start, int end = oo) { 34     node nd; 35     nd.start = start; 36     nd.end = end; 37     nd.slink = 0; 38     for (int i = 0; i < ALPHABET_SIZE; i++) 39         nd.next[i] = 0; 40     tree[++last_added] = nd; 41     return last_added; 42 } 43  44 char active_edge() { 45     return text[active_e]; 46 } 47  48 void add_SL(int node) { 49     if (needSL > 0) tree[needSL].slink = node; 50     needSL = node; 51 } 52  53 bool walk_down(int node) { 54     if (active_len >= tree[node].edge_length()) { 55         active_e += tree[node].edge_length(); 56         active_len -= tree[node].edge_length(); 57         active_node = node; 58         return true; 59     } 60     return false; 61 } 62  63 void st_init() { 64     needSL = 0, last_added = 0, pos = -1,  65     remainder = 0, active_node = 0, active_e = 0, active_len = 0; 66     root = active_node = new_node(-1, -1); 67 } 68  69 void st_extend(char c) { 70     text[++pos] = c; 71     needSL = 0; 72     remainder++; 73     while(remainder > 0) { 74         if (active_len == 0) active_e = pos; 75         if (tree[active_node].next[active_edge()] == 0) { 76             int leaf = new_node(pos); 77             tree[active_node].next[active_edge()] = leaf; 78             add_SL(active_node); //rule 2 79         } else { 80             int nxt = tree[active_node].next[active_edge()]; 81             if (walk_down(nxt)) continue; //observation 2 82             if (text[tree[nxt].start + active_len] == c) { //observation 1 83                 active_len++; 84                 add_SL(active_node); //observation 3 85                 break; 86             } 87             int split = new_node(tree[nxt].start, tree[nxt].start + active_len); 88             tree[active_node].next[active_edge()] = split; 89             int leaf = new_node(pos); 90             tree[split].next[c] = leaf; 91             tree[nxt].start += active_len; 92             tree[split].next[text[tree[nxt].start]] = nxt; 93             add_SL(split); //rule 2 94         } 95         remainder--; 96         if (active_node == root && active_len > 0) { //rule 1 97             active_len--; 98             active_e = pos - remainder + 1; 99         } else100             active_node = tree[active_node].slink > 0 ? tree[active_node].slink : root; //rule 3101     }102 }103 104 int main() {105     //106     return 0;107 }

0 0