后缀树的在线构造 On line construction of suffix tree
后缀树
后缀树的构造
算法所用符号描述 S=需要构造后缀树的字符串 Si=从第i个字符开始的后缀 N(Si)=Si在后缀树中对应的叶节点 P(Si)=N(Si)的父节点 G(Si)=P(Si)的父节点,即N(Si)的祖父 SL(p)=p的后缀连接所指向的节点 W(p, q)=从p到q所经过的字符串 root=后缀树的根节点
后缀树的构造
算法流程:定义SL(root)=root,首先插入S,此时后缀树 中仅有两个节点。 设已经插入了Si,现要插入Si+1。分情况讨论: 1)P(Si)在插入Si之前已经存在。则P(Si)有后缀连接。令 u=SL(P(Si))。从u开始沿着树往下查找,在合适的地方插 入新的节点。 2)P(Si)是在插入Si的过程中产生的。此时G(Si)必定存在 并有后缀连接。令u=SL(G(Si),w=W(G(Si),P(Si))。从u 开始,对w进行快速定位找到节点v(注意,v可能需要通 过分割边来得到)。令SL(P(Si))指向v。从v开始沿着树往 下查找,在合适的地方插入新的节点。 不断重复以上过程,即可完成整棵后缀树的构造。
后缀树的应用1
举例:在banana中查找a一次T,必定对应着一个不同的后缀, 而这所有的后缀又都有着共同的前缀T。因 此这些后缀在S的后缀树中必定属于某一棵 子树。这棵子树的叶子数便等于T在S中出 现的次数。
后缀树的应用2
举例:统计banana中出现an的次数
感性认识后缀树
banana所对应的后缀树如下:
Trie
为了更好地理解后缀树,我们先来看一种 被称为Trie的数据结构。下图是一个典型的 Trie:
Trie的定义
Trie是一种搜索树,可用于存储并查找字符 串。Trie的每一条边都对应一个字符。在 Trie中查找字符串S时,只需依次枚举S的 每个字符,同时从Trie的根节点开始选择相 应的边往下走。如果枚举完的同时到达Trie 的叶子节点,说明S存在于Trie中。如果未 到达叶子节点或者枚举中途发现没有任何 对应的边,说明S没有被包含在Trie中。
一种有效的后缀树建立方法
一种有效的后缀树建立方法
一种有效的后缀树建立方法,是一种高效的字符串查询算法,它可以快速地检索出文本中的所有子串。
后缀树也被称之为“字符串树”或“Trie树”,是一种多叉树,其中的每个节点都表示一个字符,每条路径从根节点到某个叶节点表示一个单词或字符串。
建立后缀树的过程是:首先将字符串中的每个后缀以一个节点的形式插入到树中,然后对每个节点,建立它们之间的链接。
例如,如果一个字符串包含三个后缀:“ab”,“bc”和“cd”,那么就会有3个节点,每个节点都指向另一个节点,并且从根节点到叶节点的路径就是字符串的正确排列。
后缀树的建立过程非常有效,因为它可以在线性时间内完成,即O(n)的时间复杂度,其中n是字符串的长度。
它能够有效地解决字符串中的子串查找问题,而不用去浪费大量的时间。
后缀树也可以被用在很多其他的应用场景中,比如文本搜索、文本压缩、字符串匹配等。
由于它的高效性,它已经成为解决文本搜索问题的有力工具。
用一种有效的后缀树建立方法来构建一棵后缀树,要求字符串S的长度为n,首先从第一个字符开始,将S的每
个子串都插入到树中,同时也将S的每个前缀插入到树中,当每个子串插入完毕后,就能构建出一棵完整的后缀树,其中从根节点到叶节点的路径表示S的所有子串,而从根节点到叶节点的路径上每个字符表示S的每个前缀。
建立完一棵后缀树以后,对于任意一个子串都可以以O(m)的时间复杂度在树中找到,其中m是子串的长度。
因此,后缀树可以帮助用户快速地查找文本中的所有子串。
总的来说,一种有效的后缀树建立方法非常有效,它可以帮助用户快速地查找文本中的子串,而且其建立过程的时间复杂度也很低。
SUFFIX TREE文件生成器
SUFFIX TREE文件生成器
Aleksejs Udris;刘岩
【期刊名称】《电脑知识与技术》
【年(卷),期】2016(012)013
【摘要】后缀树是一个功能强大的数据结构,可以用于计算机科学执行字符串后处理操作.使用树结构的一个挑战是,随着树的生长、树的结构变得难以想象.该文的项目就是针对后缀树的这一问题,通过使用三维空间来改善树的呈现效果.项目的目的将允许用户在没有重叠显示的情况下,大幅增加从屏幕上获得的数据量.这个项目将着眼于渲染定向图,如在双曲空间的后缀树.
【总页数】3页(P77-79)
【作者】Aleksejs Udris;刘岩
【作者单位】同济大学软件学院,上海201999;同济大学软件学院,上海201999【正文语种】中文
【中图分类】TP311
【相关文献】
1.On-line linear time construction of sequential binary suffix trees [J], Lai Huoyao;Liu Gongshen
2.数控加工后置处理通用选配文件生成器 [J], 梁振刚;郝博
3.VXI即插即用知识库文件自动生成器的研制 [J], 周泓;汪乐宇
4.关于重复词句提取的两种算法分析——基于Suffix Tree和重复序列两种算法的实验结果比较 [J], 殷波;蒋华;刘新平
5.A Chinese Web Page Clustering Algorithm Based on the Suffix Tree [J], YANGJian-wu
因版权原因,仅展示原文概要,查看原文内容请购买。
后缀树简介
该讲稿耗时约1.5小时。
1. 后缀树Gusfield : 关于字符串,树和序列的算法Weiner 73“线性模式匹配算法”IEEE 自动控制及转换会议McCreight 76 “一种节约空间的后缀树构造算法”JACM23(2) 1976Chen 和 Seifras 85“高级高效的后缀树构造”Apostolico/Galil 《关于单词的组合算法》 其它的用于字符串的“查找”结构基本问题:“模式”(长度为)到“文本”(长度为)匹配m n z 目标:判断给定的字符串(“模式”)是否是文本的子串z 可能以连接短字符串的形式产生,比如报纸z 在IR 方面的应用,还有在计算生物学方面的应用(DNA 序列)z 如果模式比文本可靠,可以构造DFA ,其运行时间与文本长度成线性关系z 如果文本比模式可靠,可以构造后缀树,其运行时间与模式长度成线性关系 z 在计算生物学中的应用第一种想法:基于字符串的二叉树。
因为对模式进行多次重复操作故而效率低下。
z 分散层叠?z 意识到每个节点只需要一个字符!Tries:z 类似桶堆的办法:用有限字母表∑。
z以前的方法:字符串字典 z树中的孩子均通过“字母表”索引 z搜索与要查找的字符串长度相等的字符串所需要的时间 z重复插入 z最优化,因为哈希需要时间。
z但是没有“哈希函数”,所以需要更好的算法 z 空间是个问题– 采用数组会增加大小为∑的存储消耗– 采用基于字母表的二叉树会增加log ∑的搜索时间– 对于“常字母表”可行– 如果真的很繁琐,可以在每个节点用哈希表z 最差情况下的大小:单词长度的总和(漂亮地解决了“字典”问题)但是子串呢?z 想法:包含所有的个子字符串的trie2n z 等价于包含所有个后缀的trien z 在末尾添上“记号”,所以其它子串没有后缀(否则,某些后缀可以是一个内部节点,被其它后缀段“隐藏”)z 意味着每个后缀一个叶子z 原始创建方法:插入每个后缀z 基本算法:– 文本1...n a a– 定义...i i s a a =n |– for =1 toi n – 插入i s z 时间,空间()O n 更好的构造方法:z 注意到trie 的大小可以变得更小:.aaaaaaa z 时间复杂度为的算法()O n z 想法:避免“存储”带来的重复操作z 还可以采用少量指针搜索树的办法——使用引用定位z 假设只插入aw z 接着插入饿是w z 的大前缀可能已经存在于trie 中w z 避免遍历:跳到前缀的末尾后缀连接:z Trie 中的任何一个节点对应字符串z 将代表的节点指向代表x 的节点ax z 假设只要插入aw z 上溯树直到找到后缀连接z 跟随后缀连接(将你带到代表的路径上)w z 沿着树向下走(增加节点)插入的剩余部分w 存储:(保存你的工作)z 可以为我们走过的所有节点增加后缀连接z (因为走完串的时候,正好走到w )aw z 实施计划:上溯一个节点以创建后缀链接z 向上遍历也适用于(相同长度的)向下遍历z 一旦节点有了后缀链接,不会再次经过z 因此,所有用于向上/下遍历所花费的时间等于后缀链接的数目z 每个节点一个后缀链接,因此时间为(||)O T 至此演讲进行了半个小时。
后缀树与后缀数组
• 显然, LCP(Suffix(i+1), Suffix(j+1)) = max(h[k]-1,0);
i i+1
j j+1
• 设i+1在sa中位置为t,sa[t+1] = p 即h[t] = LCP(suffix(i+1),suffix(p)) • 由suffix(i) < suffix (j) => suffix(i+1) < suffix(j+1) • 而suffix(p) 在sa数组中的位置紧贴着suffix(i+1),所以有 suffix(i+1) < suffix(p) <= suffix(j+1) • 而LCP(suffix(i+1),suffix(j+1)) = max(h[k]-1,0) 下标:1 2 3 4 5 Sa数组
– 当i超过字符串的长度,可以认为s[i] = -oo。
• 后缀:指从某个位置i开始到整个串结束的一个 特殊子串。字符串S的从i个字符开始的后缀记为 Suffix(i)。
– 显然,Suffix(i) = S[i..len(S)],记为S(i)
• 字符串的大小比较:例如串S与串T,从小到大 枚举i,如果s[i] < t[i] => S < T, 如果s[i] > t[i] => S > T。 两个串完全匹配则S== T
• 名次数组:名次数组Rank[i]保存的是 Suffix(i) 在所有后缀中从小到大排 列的“名次”。 可以视为大小
– 简单来说,名次数组就是问“你排第几”
• 显然,两者只要知道一个,就可以推出另外 一个
下标:1
2
后缀树构造方法讲义
后缀树讲义1.基本定义a. 后缀:一个长度为m 的序列m s s s s S .....321=,记m i i i s s s S .....1+=为S 的第i 个后缀,显然1S =S 。
b. 后缀树:一个长度为m 的序列S 的后缀树是一个有根定向树,别且满足下面条件① 它刚好有m 个叶节点。
② 除了根节点之外的每一个内节点至少有两个子节点,并且每条边都对应S 的一个非空子序列。
③ 任何从一个内节点出发的两条边对应的子序列的第一个字符都不同。
④ 每一条从根节点出发到叶子节点的路径对应序列S 的一个后缀。
第四个条件是后缀树的主要特征。
图1:序列xabxa$对应的后缀树c. 路径的标签:我们称一个路径对应的序列叫路径的标签。
d. 一个节点的标签:从根节点到这个节点的路径对应的序列。
注:并不是所有的序列都对应有后缀树,比如序列xabxa 就没有后缀树因为后缀xa 刚好是后缀xabxa 的前缀,因此标签为序列xa 的路径并不是叶节点,此时xabxa 没有后缀树,为了解决这一问题,通常我们在序列末尾加上一个$字符(不同于序列中出现的任何字符)以解决这个问题,因为此时任何一个后缀都不可能是另外一个后缀的前缀。
e. 隐含后缀树:序列S 的隐含后缀树指的是,序列S$的后缀树去掉那些有$的边上的$符号,然后将空白的边去掉得到的树。
图2:xabxa 的隐含后缀树。
2.后缀树的构造后缀树的构造方法有很多种,其中Ukkonen ’s 算法是最容易理解的而且其时间和空间复杂度都是线性的,这里我们只讲这种算法。
该算法根据S 的前缀i s s s s .....321构建一个隐含后缀树i Γ,当I =m 的时候m Γ就是S 的后缀树,因此Ukkonen ’s 算法可以被分成m 个阶段,在第I+1个阶段,根据i Γ构建树1+Γi ,而每一个阶段又被分成I +1个扩展,其中的第j 个扩张确认S[j,j+1…I +1],11+≤≤i j ,即].....1[i S 序列的第j 个后缀在树中。
后缀树的构造方法-Ukkonen详解
最近在学习后缀树的构造,在网上找了好久发觉国内详解它的构造的文章胜少,在苦苦寻觅了许久,终于发现了一个网友翻译的一篇文章,很好,于是我转帖出来,希望能有更多的人受益,也希望国内多一些英文高手多翻译一些国外的技术文章,好让我们这些英文很烂的人受益,呵呵!后缀树Fast String Searching With Suffix Trees原著Mark Nelson. Fast string searching with suffix trees. 1996.构造法E. Ukkonen. On-line construction of suffix trees. 1995.翻译3xian / 三鲜in GDUT三鲜序原来是打算翻译SartajSahni的Suffix tree, 并专注地进行了一周, 连复习备考的时间也不惜占去. 我希望给国产的同好者提供更通俗易懂的资料, 在翻译的同时对原文进行了删改, 并加入了许多自己的心得. 然而后来发现了Mark Nelson的这篇文章, 相比之下更有亲和力, 于是老实地尽弃前功来翻译这篇. 更重要一个原因是, Mark Nelson介绍的是Ukkonen的构造法O(n), 它比SartajSahni的构造法O(nr), r为字母表大小在时间上更有优势. 但我们不能说SartajSahni的算法慢, 因为r往往会很小, 因此实际效率也接近线性, 两种构造法在思想上均有可取之处.本文偏重于阐述后缀树的构造过程, 而并没有直接介绍后缀树除了匹配以外还能做什么. 其实后缀树是一种功能非常强大的数据结构, 你可以去搜索引擎了解一下它还有多少功能, 当然我最希望的是你在阅读本文之后已经足以体会后缀树的妙处, 日后遇到诸多问题的时候都能随心随意地用上.最后唠叨一句. 我所见过的各种介绍后缀树的论文都难免使初学者陷入混乱, 本文估计也好不到哪里去. 这在一定程度上说明了后缀树的原理是不太浅显的, 理解它需要在整体上把握, 建议希望读者先不要纠结于细节, 思路不清则反复阅读.问题的来源字符串匹配问题是程序员经常要面对的问题. 字符串匹配算法的改进可以使许多工程受益良多, 比如数据压缩和DNA排列. 这篇文章讨论的是一种相对鲜为人知的数据结构--- 后缀树, 并介绍它是如何通过自身的特性去解决一些复杂的匹配问题.你可以把自己想象成一名工作于DNA排列工程的程序员. 那些基因研究者们天天忙着分切病毒的基因材料, 制造出一段一段的核苷酸序列. 他们把这些序列发到你的服务器里, 指望你在基因数据库中定位. 要知道, 你的数据库里有数百种病毒的数据, 而一个特定的病毒可以有成千上万的碱基. 你的程序必须像C/S工程那样实时向博士们反馈信息, 这需要一个很好的方案.很明显, 在这个问题上采取暴力算法是极其低效的. 这种方法需要你在基因数据库里对比每一个核苷酸, 测试一个较长的基因段基本会把你的C/S系统变成一台古老的批处理机.直觉上的解决方法由于基因数据库一般是不变的, 通过预处理来把搜索简化或许是个好主意. 一种预处理的方法是建立一棵Trie. 我们通过Trie引申出一种东西叫作后缀Trie. (后缀Trie离后缀树仅一步之遥.) 首先, Trie是一种n叉树, n为字母表大小, 每个节点表示从根节点到此节点所经过的所有字符组成的字符串. 而后缀Trie的“后缀” 说明这棵Trie包含了所给字段的所有后缀(也许正是一个病毒基因).图1BANANAS的后缀Trie图1展示了文本BANANAS的后缀Trie. 关于这棵Trie有两个地方需要注意. 第一, 从根节点开始, BANANAS的每一个后缀都插入到Trie中, 包括BANANAS, ANANAS, NANAS, ANAS, NAS, AS, S. 第二, 鉴于这种结构, 你可以通过从根节点往下匹配的方式搜索到单词的任何一个子串.这里所说的第二点正是我们认为后缀Trie优秀的原因. 如果你输入一个长度为N的文本并想在其中搜索一个长度为M的串, 传统的暴力匹配需要进行N*M次字符对比, 而一些改进过的匹配技术, 比如像Boyer-Moore算法, 可以在O(N+M)的时间开销内解决问题, 平均效率更是令人满意. 然而, 后缀Trie亮出了O(M)的牌子, 彻底鄙视了其他算法的成绩, 后缀Trie对比的次数仅仅相当于被搜索串的长度!这确实是可圈可点的威力, 这意味着你能通过仅仅7次对比便在莎士比亚所有作品中找出BANANAS. 但有一点我们可不能忘了, 构造后缀Trie也是需要时间的.后缀Trie之所以没有家喻户晓正是因为构造它需要O(n2)的时间和空间. 平方级的开销使它在最需要它的领域--- 长串搜索中被拒之门外.横空出世直到1976年, Edward McCreigh发表了一篇论文, 咱们的后缀树问世了. 后缀Trie的困境被彻底打破.后缀树跟后缀Trie有着一样的布局, 但它把只有一个儿子的节点给剔除了. 这个过程被称为路径压缩, 这意味着树上的某些边将表示一个序列而不是单独的字符.图2BANANAS的后缀树图2是由图1的后缀Trie转化而来的后缀树. 你会发现这树基本还是那个形状, 只是节点变少了. 在剔除了只有一个儿子的节点之后, 总节点数由23降为11. 经过证明, 在最坏情况下, 后缀树的节点数也不会超过2N (N为文本的长度). 这使构造后缀树的线性时空开销成为可能.然而, McCreight最初的构造法是有些缺陷的, 原则上它要按逆序构造, 也就是说字符要从末端开始插入. 如此一来,便不能作为在线算法, 它变得更加难以应用于实际问题, 如数据压缩.20年后, 来自赫尔辛基理工大学的EskoUkkonen把原算法作了一些改动, 把它变成了从左往右. 本文接下来的所有描述和代码都是基于EskoUkkonen的成果.对于所给的文本T, EskoUkkonen的算法是由一棵空树开始, 逐步构造T的每个前缀的后缀树. 比如我们构造BANANAS的后缀树, 先由B开始, 接着是BA, 然后BAN, … . 不断更新直到构造出BANANAS的后缀树.图3逐步构造后缀树初窥门径加入一个新的前缀需要访问树中已有的后缀. 我们从最长的一个后缀开始(图3中的BAN), 一直访问到最短的后缀(空后缀). 每个后缀会在以下三种节点的其中一种结束.l 一个叶节点. 这个是常识了, 图4中标号为1, 2, 4, 5的就是叶节点.l 一个显式节点. 图4中标号为0, 3的是显式节点, 它表示该节点之后至少有两条边.l 一个隐式节点. 图4中, 前缀BO, BOO, 或者非前缀OO, 它们都在某条表示序列的边上结束, 这些位置就叫作隐式节点. 它表示后缀Trie中存在的由于路径压缩而剔除的节点. 在后缀树的构造过程中, 有时要把一些隐式节点转化为显式节点.图4加入BOOK之后的BOOKKEEPER(也就是BOOK的后缀树)如图4, 在加入BOOK之后, 树中有5个后缀(包括空后缀). 那么要构造下一个前缀BOOKK的后缀树的话, 只需要访问树中已存在的每一个后缀, 然后在它们的末尾加上K.前4个后缀BOOK, OOK, OK和K都在叶节点上结束. 由于我们要路径压缩, 只需要在通往叶节点的边上直接加一个字符, 而不需要创建一个新节点.在所有叶节点更新之后, 我们还需要在空后缀后面加上K. 这时候我们发现已经存在一条从0节点出发的边的首字符为K, 没必要画蛇添足了. 换句话说, 新加入的后缀K可以在0节点和2节点之间的隐式节点中找到. 最终形态见图5.图5加入BOOKK之后的BOOKKEEPER相比图4, 树的结构没有发生变化如果你是一位敏感的读者, 可能要发问了, 如果加入K我们什么都不做的话, 在查找的时候如何知道它到底是一个后缀呢还是某个后缀的一截? 如果你同时又是一位熟悉字符串算法的朋友, 心里可能马上就有答案了--- 我们只需要在文本后面加个字母表以外的字符, 比如$或者#. 那我们查找到K$或K#的话就说明这是一个后缀了.稍微麻烦一点的事情从图4到图5这个更新过程是相对简单的, 其中我们执行了两种更新: 一种是将某条边延长, 另一种是啥都不做. 但接下来往图5继续加入BOOKKE, 我们则会遇到另外两种更新:1. 创建一个新节点来割开某一隐式节点所处的边, 并在其后加一条新边.2. 在显式节点后加一条新边.图6先分割, 再添加当我们往图5的树中加入BOOKKE的时候, 我们是从已存在的最长后缀BOOKK开始, 一直操作到最短的后缀空后缀. 更新最长的后缀必然是更新叶节点, 之前提到了, 非常简单. 除此之外, 图5中结束在叶节点上的后缀还有OOKK, OKK, KK. 图6的第一棵树展示了这一类节点的更新.图5中首个不是结束在叶节点上的后缀是K. 这里我们先引入一个定义:在每次更新后缀树的过程中, 第一个非叶节点称为激活节点. 它有以下性质:1. 所有比激活节点长的后缀都在叶节点上结束.2. 所有在激活节点之后加入的后缀都不在叶节点上结束.后缀K在边KKE上的隐式节点结束. 在后缀树中我们要判断一个节点是不是非叶节点需要看它是否有跟待加入字符相同的儿子, 即本例中的E.一眼可以看出, KKE中的第一个K只有一个儿子: K. 所以它是非叶节点(这里同时也是激活节点), 我们要给他加一个儿子来表示E. 这个过程有两个步骤:1. 在第一个K和第二个K之间把边分割开, 于是第一个K(隐式节点)成了一个显式节点, 如图6第二棵树.2. 在刚刚变身而来的显式节点后加一个新节点表示E, 如图6第三棵树. 由此我们又多了一个叶节点.后缀K更新之后, 别忘了还有空后缀. 空后缀在根节点(节点0)结束, 显然此时根节点是一个显式节点. 我们看一下它后面有没有以E开头的边---没有, 那么加入一个新的叶节点(如果存在以E开头的边, 则不用任何操作). 最终如图7.图7归纳, 反思, 优化借助后缀树的特性, 我们可以做出一个相当有效的算法. 首先一个重要的特性是: 一朝为叶, 终生为叶. 一个叶节点自诞生以后绝不会有子孙. 更重要的是, 每当我们往树上加入一个新的前缀, 每一条通往叶节点的边都会延长一个字符(新前缀的最后一个字符). 这使得处理通往叶节点的边变得异常简单, 我们完全可以在创建叶节点的时候就把当前字符到文本末的所有字符一股脑塞进去. 是的, 我们不需要知道后面的字符是啥, 但我们知道它们最终都要被加进去. 因此, 一个叶节点诞生的时候, 也正是它可以被我们遗忘的时候. 你可能会担心通往叶节点的边被分割了怎么办, 那也不要紧, 分割之后只是起点变了, 尾部该怎么着还是怎么着.如此一来,我们只需要关心显式节点和隐式节点上的更新.还要提到一个节约时间的方法. 当我们遍历所有后缀时, 如果某个后缀的某个儿子跟待加字符(新前缀最后一个字符)相同, 那么我们当前前缀的所有更新就可以停止了. 如果你理解了后缀树的本质, 你会知道一旦待加字符跟某个后缀的某个儿子相同, 那么更短的后缀必然也有这个儿子. 我们不妨把首个这样的节点定义为结束节点. 比结束节点长的后缀必然是叶节点, 这一点很好解释, 要么本来就是叶节点, 要么就是新创建的节点(新创建的必然是叶节点). 这意味着, 每一个前缀更新完之后, 当前的结束节点将成为下一轮更新的激活节点.好了, 现在我们可以把后缀树的更新限制在激活节点和结束节点之间, 效率有了很大的改善. 整理成伪代码如下:PLAIN TEXTC:1. Update( 新前缀)2. {3. 当前后缀= 激活节点4. 待加字符= 新前缀最后一个字符5. done = false;6. while ( !done ) {7. if ( 当前后缀在显式节点结束) {8. if ( 当前节点后没有以待加字符开始的边)9. 在当前节点后创建一个新的叶节点10. else11. done = true;12. } else {13. if ( 当前隐式节点的下一个字符不是待加字符) {14. 从隐式节点后分割此边15. 在分割处创建一个新的叶节点16. } else17. done = true;18. if ( 当前后缀是空后缀)19. done = true;20. else21. 当前后缀= 下一个更短的后缀22. }23. 激活节点= 当前后缀24. }后缀指针上面的伪代码看上去很完美, 但它掩盖了一个问题. 注意到第21行, “下一个更短的后缀”, 如果呆板地沿着树枝去搜索我们想要的后缀, 那这种算法就不是线性的了. 要解决此问题, 我们得附加一种指针: 后缀指针. 后缀指针存在于每个结束在非叶节点的后缀上, 它指向“下一个更短的后缀”. 即, 如果一个后缀表示文本的第0到第N个字符, 那么它的后缀指针指向的节点表示文本的第1到第N个字符.图8是文本ABABABC的后缀树. 第一个后缀指针在表示ABAB的节点上. ABAB的后缀指针指向表示BAB的节点. 同样地, BAB也有它的后缀指针, 指向AB. 如此这般.图8加上后缀指针(虚线)的ABABABC的后缀树介绍一下如何创建后缀指针. 后缀指针的创建是跟后缀树的更新同步的. 随着我们从激活节点移动到结束节点, 我把每个新的叶节点的父亲的路径保存下来. 每当创建一条新边, 我同时也在上一个叶节点的父亲那儿创建一个后缀指针来指向当前新边开始的节点. (显然, 我们不能在第一条新边上做这样的操作, 但除此之外都可以这么做.)有了后缀指针, 就可以方便地一个后缀跳到另一个后缀. 这个关键性的附加品使得算法的时间上限成功降为O(N).参考文献E.M. McCreight. A space-economical suffix tree construction algorithm. Journal of the ACM, 23:262-272, 1976.E. Ukkonen. On-line construction of suffix trees.Algorithmica, 14(3):249-260, September 1995.来源:/lazy_p/blog/static/13510721620108139476816/。
窗外一叶?后缀树与后缀数组
窗外一叶?后缀树与后缀数组本文系转载:/post/hj7cv6m后缀树和后缀数组简直就是ACM 选手必备的知识啊,我已经在两次比赛中碰到过相关的问题了。
我甚至还写过一篇应用的文章,可是我真是井底之蛙啊,那时我还不知道这个叫后缀数组,还有更好的构造算法,还有很多的应用。
最近终于好好在这方面扫了个盲,在此小小地总结一下。
假设有一个长度为 n 的字符串T[0 … n);S(i) 表示 T 的从下标 i 开始的后缀,即T[i … n)。
那么 T 的后缀数组就是把 S(i) ~ S(n – 1) 这n 个后缀按字典序排好序的一个数组。
它对于查找T 的子串之类的问题是很有用的。
问题就在于怎样快速地把这些后缀排好序。
最简单的方法就是把所有S(i) 快速排序。
快速排序本身的时间是O(n log n),但是由于排序的对象是一个个字符串,所以每次比较的时间在最差情况下都会变成线性的(也就是 O(n) 的),因此总的时间在最差情况下可能会升到O(n2) 左右,这就很慢了。
对此,我学到了三个更快的算法。
1. Ukkonen 算法Ukkonen 算法先用 O(n) 的时间构造一棵后缀树,然后再用 O(n) 的时间从后缀树得到后缀数组。
在这个网址,介绍了作者Esko Ukkonen,并列出了他的一些论文;其中的一篇《On-line construction of suffix-trees》是可以下载的,里面就讲解了什么是后缀树,怎样在 O(n) 的时间内构造它,以及怎样从它得到后缀数组。
不过我一开始还没发现这篇论文,我是从Dan Gusfield 的《Algorithms on Strings, Trees and Sequences –COMPUTER SCIENCE AND COMPUTATIONAL BIOLOGY》这本书里学到这个算法的。
这本书在中国没的卖,想买的话,可以找代购网站去Amazon 买。
我是在 eMule 上搜到并下载的。
后缀数组及其应用
其中l=2^k,且 l>=n
O(n)排序 。。。 。。。
2-rank[]
2-sa[]
8-rank[] 8-sa[] O(n)排序 4-rank[] 4-sa[] O(n)排序
算法最多进行logn次,每次 时间为O(N).所以最终时间 复杂度为O(nlogn)
倍增算法——思想总结
1、定义k-rank[],并利用k-rank[]求出2k-rank[],高 效的比较;充分利用了后缀之间的联系;
缺点: 该算法实现过于复杂,细节较多,且代码量较长,对于 一名信息竞赛的选手而言,在短短的5个小时内,要完全正 确地写出后缀树谈何容易。在各种各样的比赛中,我们要 选择实现相对简单,且效率较高的算法和数据结构。
随着广大OIERS的呼声,
后缀数组横空出世!!
*后缀数组 (Suffix Arrary)
后缀树的特点: 后缀树实质上是一棵字典树。从根节点到任意叶子节点 都对应了原串的一个后缀。但与Trie结构不同的是树上的每一 条边记录的不是单个字母,而是一个字符串。这样一来,将 空间从Trie的O(N^2)降到O(N) 。 同时,Trie的构造时间是O(N^2),而后缀树的构造时间仅 为O(N)。这也是后缀树广为人知的原因。 下面我们看个实例:
后缀数组的构造方法:
比较直观的想法——暴力排序: 把n个后缀预处理出来,快排,堆排,归并等,时 间复杂度O(nlogn),看上去还不错。 但仔细想想,两个字符串间比较有个strcmp(),这 明显不是线性。况且kmp也需要O(N+M)的时间,这样 一来时间复杂度接近O(n^2)。 当N>10000时就悲剧了。。。。。
通过上面的例子,通过k-rank[]可以在O(1)的时间内 完成suffix(i)和suffix(j)的比较。这样就充分利用了后缀之 间有机的联系。 具体实现当然不是枚举每两个串进行比较。进一步想 想,这样比较不就是将每个后缀看作一个元素,k-rank[i]作 为第一关键字,k-rank[i+k]作为第二关键字进行排序吗? 这时,排序方法又是一个大问题。虽然快排等比较型排序 可以做到O(nlogn),但是还不能满足后缀树组的高效要求。 每个元素只有两个关键字,而且关键字分布比较集中,相信聪 明的你已经想到了:
一种基于后缀树的中文网页层次聚类方法
• 实验结果表明该方法提高了聚类精度,同 时避免了单链接算法的链式效应。
6 参考文献
• [1] O. Zamir, O. Etzioni. Web document clustering: A feasibility demonstration[C]. In: Proceedings of SIGIR, 1998:46–54.
• 研究了STC算法和STCC算法的精度。
• 实验的结果如图2所示。
• 从图2中可以看出,STCC算法的精度比STC 算法提高了将近10%左右,只有第三个查询 (qq)的精度相差不大,这是因为其搜索结果 中含有大量的特殊符号。
4结论
• 在STC算法基础上,使用雅克比系数计算 基本类的相似度,使基本类相似度为介于0 和1之间的值,产生基本类相似度矩阵,进 而采用变色龙算法完成网页的层次聚类。
• 后缀树的特点是: •只有一个根节点 •中间节点至少有两个子节点 •每条边用子串标识,表示节点到根的路径 •同一节点的边不能有相同的标识 •每个字符串的子串都有相对应的后缀节点
• 图1是三个字符串 “cat ate cheese”, “mouse ate cheese too” 和 “cat ate mouse too”形成的后缀树的例子。
• 有代表性的网页聚类方法包括:Sanderson和 Croft提出的根据概念之间包含关系的聚类方法;
• Lawrie 提出的利用基于条件概率的语言模型聚类 的方法;
后缀数据结构
听说是后缀数组的经典题?!QAQ
Poj1743—最长不重叠重复子串 Poj3294—出现次数超过一半的最长子串 Poj3261—重复k次可重叠子串。 SPOJ694/705 本质不同的子串个数 最长回文子串 Poj2406—字符串最小循环节 Poj3693—连续重复次数最多的子串 Poj 2774 –两(任意多)个串的最长公共连续子串 Poj3415—长度不小于K的公共子串个数(*) Spoj220—每个字符串中至少出现两次且不重叠的最长子串 —出现或反转后出现在至少K个字符串中的最长子串 BZOJ3172: [Tjoi2013]单词 BZOJ3230: 相似子串 BZOJ3238: [Ahoi2013]差异
广义后缀树
对于字符串集合T={t1,t2…tn}的广义后缀树,是一个 压缩字典树(trie)其中包含了T中每一个字符串的所有的后 缀。 简而言之,就是一个将所有字符串的所有后缀插入Trie树 并进行路径压缩之后形成的树结构。
广义后缀树的附加记录信息
①包含后缀树的所有信息 ②通过记录每个后缀结点的属于哪个字符串可以对不同串分类统 计 ③广义后缀树中存在的字符串至少一个字符串的子串。 ④字符串出现了几次→广义后缀树中对应结点所在子树后缀结点 数
具有神奇性质的题
BZOJ1031: [JSOI2007]字符加密Cipher BZOJ1396: 识别子串 BZOJ2780: [Spoj]8093 Sevenk Love Oimaster BZOJ2806: [Ctsc2012]Cheat BZOJ3277: 串 BZOJ3473: 字符串 BZOJ3413: 匹配 BZOJ3676: [Apio2014]回文串 BZOJ2119: 股市的预测 Codeforces 316G3 Good Substrings BZOJ2555: SubString(LCT) 支持往后+个字符,询问一个串出现次数,回到第i个操作结束 的时候,必须在线.(总长度<=20w,操作数<=10w,时限 3s.)
后缀树的设计与构造
2019年第6期信息与电脑China Computer & Communication算法语言后缀树的设计与构造赵美勇 史昊臻 朱珍珍(山东科技大学,山东 济南 266590)摘 要:后缀树是处理字符串的一个优秀算法。
利用图像化设计可使后缀树更加清晰。
按照递推的思路,建立前i 个字符对应的后缀树,通过插入第i+1个字符的方式,建立前i+1个字符对应的后缀树。
由于字符串的任意子串都可以表示为某个后缀的前缀,因此可以设定当前节点为根节点。
父节点取子节点中贡献最大的节点,同时,记录其对应的字符串。
关键词:后缀树;数据结构;时间复杂度中图分类号:TP399 文献标识码:A 文章编号:1003-9767(2019)06-052-02Design and Construction of Suffix TreesZhao Meiyong, Shi Haozhen, Zhu Zhenzhen(Shandong University of Science and Technology, Jinan Shandong 266590, China)Abstract: Suffix tree is an excellent algorithm for string processing. The suffix tree can be clearer by image design. According tothe recursive thinking, the suffix tree corresponding to the first I characters is established, and the suffix tree corresponding to the first I + 1 characters is established by inserting the first I + 1 characters. Since any substring of a string can be represented as a prefix of a suffix, the current node can be set as the root node. The parent node takes the node that contributes the most to the child node, and records its corresponding string.Key words: suffix tree; data structure; time complexity0 引言字符串处理是计算机中很重要的问题,尤其在自然语言处理中。
后缀树简介
后缀树一、字符串匹配1、字符串匹配问题的形式定义●文本(Text)是一个长度为n的数组T[1...n];●模式(Pattern)是一个长度为m且m≤n的数组P[1…m];●T和P中的元素都属于有限的字母表(alphabet);●如果0≤s≤n-m,并且T[s+1…S+m]=P[1…m],即对1≤j≤m,有T[s+j]=P[j],则说模式P在文本T中出现且位移为s,且称s是一个有效位移(validshift)。
如上图中,目标是找出所有在文本T=abcabaabcabac中模式P=abaa的所有出现。
该模式在此文中仅出现一次,即在位移s=3处,位移s=3是一个有效位移。
2、解决字符串匹配问题的常见算法●朴素的字符串匹配算法(NativeStringMatchingAlgorithm)●Knuth-Morris-Pratt字符串匹配算法(即KMP算法)●Boyer-Moore字符串匹配算法字符串匹配算法通常分为两个步骤:预处理(Preprocessing)和匹配(Matching)。
所以算法的总运行时间为预处理和匹配的时间的总和。
下面描述了常见字符串匹配算法的预处理和匹配时间。
上述字符串匹配算法均是通过对模式(Pattern)字符串进行预处理的方式来加快搜索速度。
对Pattern进行预处理的最优复杂度为O(m),其中m为Pattern字符串的长度。
而后缀树(SuffixTree)是一种对Text进行预处理的字符串匹配算法。
二、字典树(Trie)1、字典树定义字典树(Trie):是一种很特别的树状信息检索数据结构,如同其名,它的构成就像一本字典,可以让你快速的进行字符插入,字符搜索等。
字典树的核心思想是空间换时间,所以数据结构本身比较消耗空间。
但它利用了字符串的共同前缀(CommonPrefix)作为存储依据,以此来节省存储空间,并加速搜索时间。
Trie的字符串搜索时间复杂度为O(m),m 为最长字符串的长度,其查询性能与集合中的字符串的数量无关。
后缀树——精选推荐
后缀树后缀树内容提要本章主要介绍了后缀树的来源以及后缀树的应⽤背景,给出了后缀树的定义、性质、特征以及构造⽅法等理论基础,通过最长回⽂的查找、⼦串的查找等实例进⼀步说明了后缀树的特征及⽤途。
引⾔在计算机科学中,后缀树(也叫做PA T树,早期的形式是位置树)是⼀种数据结构,在某种程度上,它可以显⽰出⼀个给定字符串的后缀,且对于很多的字符串操作它能够⾮常快的实现。
字符串S的后缀树是这样⼀棵树,它的所有边都是⽤字符串来标⽰的,这样字符串S 的每⼀后缀都恰好的对应⼀条从根到叶⼦节点的路径。
这是以字符串S为后缀的基数树,更具体地说,这是⼀颗帕特⾥夏树。
为字符串S构造⼀颗这样的树耗费的时间和空间与字符串的长度呈线性关系。
这样的树⼀旦构造完成,⼏个操作能够被很快的执⾏,例如,在字符串S中定位⼀个字串,在允许⼀定数量的错误前提下定位⼀个字串,为⼀个标准表达式模式定位匹配的问题等等。
后缀树也为最⼤公共字串问题提供了⼀个第⼀线性时间的解决⽅案。
这种速度的提升带来了⼀定的开销:存储⼀个字符串的后缀树⽐存储字符串本⾝需要更⼤的空间。
历史在1973年,后缀树的概念是以位置树的形式被weiner⾸先提出来,随后Donald Knuth 称它为1973年的年度算法。
分别在1976年和1995年,McCreight和Ukkonen对它的结构进⾏了很⼤程度的简化。
Ukkonen提供了后缀树的第⼀个⽹络建设,即现在熟知的Ukkonen 算法,它是运⾏时间是最快的算法。
对于恒定⼤⼩的字母表来说,这些算法的运⾏时间都是线性的,并且⼀般情况下,它们的最坏的运⾏时间是O(n long n)。
在1997年Farach给出了第⼀个后缀树构造算法,对于所有的字母表,它都是最佳的。
特别的,对来⾃于⼀个多项式范围内的⼀个整数的字母表的字符串,这是第⼀个线性时间算法。
Farach算法成为了构造后缀树和后缀树组的新算法的基础,例如,在外部存储器中,它是压缩的和简洁的。
后缀树和后缀数组
后缀树和后缀数组基本概念子串:字符串S的子串S[i..j],i?j,表示S串中从i到j这一段,也就是顺次排列S[i],S[i+1],...,S[j]形成的字符串。
字符集:一个字符集Σ是一个建立了全序关系的集合,也就是说,Σ中的任意两个不同的元素α和β都可以比较大小,要么α<β,要么β<α(也就是α>β)。
字符集Σ中的元素称为字符。
字符串:一个字符串S是将n个字符顺次排列形成的数组,n称为S的长度,表示为len(S)。
S的第i个字符表示为S[i]。
子串:字符串S的子串S[i..j],i?j,表示S串中从i到j这一段,也就是顺次排列S[i],S[i+1],...,S[j]形成的字符串。
后缀:后缀是指从某个位置i开始到整个串末尾结束的一个特殊子串。
字符串S 的从i开头的后缀表示为Suffix(i),也就是Suffix(i)=S[i..len(S)] 例如S = mississippi,那么它的所有后缀为:Suffix(1) = mississippi = SSuffix(2) = ississippiSuffix(3) = ssissippiSuffix(4) = sissippiSuffix(5) = issippiSuffix(6) = ssippiSuffix(7) = sippiSuffix(8) = ippiSuffix(9) = ppiSuffix(10) = piSuffix(11) = iSuffix(12) = (empty)不难发现,S的任意一个子串一定是某一个后缀的前缀。
字符串的大小比较:指通常所说的“字典顺序”比较,也就是对于两个字符串u、v,令i从1开始顺次比较u[i]和v[i],如果u[i]=v[i]则令i加1,否则若u[i]<v[i]则认为u<v,u[i]>v[i]则认为u>v(也就是v<u),比较结束。
如果i>len(u)或者i>len(v)仍比较出结果,那么若len(u)<len(v)则认为u<v,若len(u)=len(v)则认为u=v,若len(u)>len(v)则u>v。
前缀树的构建和查询
前缀树的构建和查询前缀树,也被称为字典树或者Trie树,是一种广泛应用于字符串处理的数据结构。
它可以用来高效地存储和查询大量字符串,尤其适用于自动补全、搜索引擎以及字典等场景。
本文将介绍前缀树的构建和查询算法,并探讨它们的应用和性能。
一、前缀树的构建前缀树的构建是基于字符串的特性来实现的。
它由根节点开始,每个节点包含多个子节点,子节点对应着字符串的一个字符。
通过遍历字符串,将每个字符添加到相应的节点上,直到字符串结束。
在构建过程中,需要注意以下几个关键步骤:1. 创建根节点:初始化一个空节点,并将其设为根节点。
2. 插入字符串:遍历待插入的字符串,对于每个字符,检查当前节点的子节点是否包含该字符。
如果包含,则移动到对应的子节点;如果不包含,则创建一个新的子节点,并将该字符添加到子节点中。
3. 标记终止节点:遍历完整个字符串后,将最后一个节点标记为终止节点,表示该节点对应的字符串已经插入完毕。
通过重复上述步骤,可以构建出一个完整的前缀树。
构建前缀树的时间复杂度为O(m*n),其中m为字符串的平均长度,n为字符串的个数。
二、前缀树的查询前缀树的查询主要有两种方式:全匹配查询和前缀匹配查询。
1. 全匹配查询:在前缀树中查找一个完整的字符串。
从根节点开始,逐个字符匹配,如果匹配成功,则移动到该字符对应的子节点;如果匹配失败,则说明该字符串不存在于前缀树中。
2. 前缀匹配查询:在前缀树中查找以给定字符串为前缀的所有字符串。
同样从根节点开始,逐个字符匹配,如果匹配成功,则移动到该字符对应的子节点;如果匹配失败,则说明该前缀对应的字符串不存在于前缀树中。
查询的时间复杂度取决于查询的字符串的长度。
对于全匹配查询,最坏情况下的时间复杂度为O(k),其中k为查询字符串的长度。
对于前缀匹配查询,最坏情况下的时间复杂度为O(p+m),其中p为查询字符串的长度,m为匹配到的字符串的平均长度。
三、前缀树的应用和性能前缀树广泛应用于自动补全、搜索引擎和字典等场景。
后缀树及模式匹配共80页文档
71、既然我已经踏上这条道路,那么,任何东西都不应妨碍我沿着这条路走下去。——康德 72、家庭成为快乐的种子在外也不致成为障碍物但在旅行之际却是夜间的伴侣。——西塞罗 73、坚持意志伟大的事业需要始终不渝的精神。——伏尔泰 74、路漫漫其修道远,吾将上下而求索。——屈原 75、内外相应,言行相称。——韩非
后缀树及模式匹配
•
46、寓形宇内复几时,曷不委心任去 留。
•
47、采菊东篱下,悠然见南山。
•
48、啸傲东轩下,聊复得此生。
•49、勤学如春起之苗源自不见其增,日 有所长 。•
50、环堵萧然,不蔽风日;短褐穿结 ,箪瓢 屡空, 晏如也 。
谢谢你的阅读
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
9 1995Springer-Verlag YorkInc. New
Algorithmica
On-Line Construction of Suffix Trees I
E. U k kቤተ መጻሕፍቲ ባይዱo n e n z
Abstract. An on-line algorithm is presented for constructing the suffix tree for a given string in time linear in the length of the string. The new algorithm has the desirable property of processing the string symbol by symbol from left to right. It always has the suffix tree for the scanned part of the string ready. The method is developed as a linear-time version of a very simple algorithm for (quadratic size) suffix tries. Regardless of its quadratic worst case this latter algorithm can be a good practical method when the string is not too long. Another variation of this method is shown to give, in a natural way, the well-known algorithms for constructing suffix automata (DAWGs).
. . .
1 This research was supported by the Academy of Finland and by the Alexander von Humboldt Foundation (Germany). 2 Department of Computer Science, University of Helsinki, P.O. Box 26 (Teollisuuskatu 23), FIN-00014 University of Helsinki, Finland. ukkonen@cs.Helsinki.FI. Received March 4, 1993; revised December 8, 1993. Communicated by K. Mehlhorn.
250
E. Ukkonen
Our algorithm is best understood as a linear-time version of another algorithm from [12] for (quadratic-size) suffix tries. The latter very elementary algorithm, which resembles the position tree algorithm in [8], is given in Section 2. Unfortunately, it does not run in linear time--it takes time proportional to the size of the suffix trie which can be quadratic. However, a rather transparent modifica, tion, which we describe in Section 4, gives our on-line, linear-time method for suffix trees. This also offers a natural perspective which makes the linear-time suffix tree construction understandable. We also point out in Section 5 that the suffix trie augmented with the suffix links gives an elementary characterization of the suffix automata (also known as directed acyclic word graphs or DAWGs). This immediately leads to an algorithm for constructing such automata. Fortunately, the resulting method is essentially the same as already given in [4]-[6]. Again it is felt that our new perspective is very natural and helps in understanding the suffix automata constructions.
Key Words. Linear-time algorithm, Suffix tree, Suffix trie, Suffix automaton, DAWG.
1. Introduction. A s u f f i x t r e e is a trie-like data structure representing all suffixes of a string. Such trees have a central role in m a n y algorithms on strings, see, e.g., [3], [7], and [2]. It is quite c o m m o n l y felt, however, that the linear-time suffix tree algorithms presented in the literature are rather difficult to grasp. The main purpose of this paper is an attempt to develop an understandable suffix tree construction based on a natural idea that seems to complete our picture of suffix trees in an essential way. The new algorithm has the i m p o r t a n t property of being on-line. It processes the string symbol by symbol from left to right, and always has the suffix tree for the scanned part of the string ready. The algorithm is based on the simple observation that the suffixes of a string T i = t 1 . " ti can be obtained from the suffixes of string T ~-~ = t~ t i_ 1 by catenating symbol t i at the end of each suffix of T ~- 1 and by adding the empty suffix. The suffixes of the whole string T = T" = q t 2 " " t , can be obtained by first expanding the suffixes of T O into the suffixes of T 1 and so on, until the suffixes of T are obtained from the suffixes of T"-1. This is in contrast with the m e t h o d by Weiner [13] that proceeds right to left and adds the suffixes to the tree in increasing order of their length, starting from the shortest suffix, and with the m e t h o d by McCreight [9] that adds the suffixes to the tree in decreasing order of their length. It should be noted, however, that despite the clear difference in the intuitive view on the problem, our algorithm and McCreight's algorithm are in their final form functionally rather closely related.
2. Constructing Suffix Tries. Let T = tit 2 ... t, be a string over an alphabet E. Each string x such that T = uxv for some (possibly empty) strings u and v is a substring of T, and each string T~ = t i . . . t, where 1 _< i ___n + 1 is a suffix of T; in particular, T~+1 = e is the e m p t y suffix. The set of all suffixes of T is denoted a(T). The suffix trie of T is a trie representing a(T). More formally, we denote the suffix trie of T as S T r i e ( T ) = (Q • { / } , root, F, g, f ) and define such a trie as an augmented DFA (deterministic finite-state automaton) which has a tree-shaped transition graph representing the trie for a(T) and which is augmented with the so-called suffix function f and auxiliary state / . The set.Q of the states of STrie(T) can be put in a one-to-one correspondence with the substrings of T. We denote by s the state that corresponds to a substring x. The initial state root corresponds to the empty string e, and the set F of the final states corresponds to a(T). The transition function g as defined as g(2, a) = y for all 2, y in Q such that y = xa, where a ~ E. The suffix function f is defined for each state ~ e Q as follows. Let ~ ~ root. Then x = ay for some a ~ E, and we set f ( 2 ) = Y. Moreover, f(root) = _1_. Auxiliary state _1_ allows us to write the algorithms in what follows such that an explicit distinction between the empty and the nonempty suffixes (or, between root and the other states) can be avoided. State _L is connected to the trie by g(L, a) = root for every a ~ E. We leave f ( • undefined. (Note that the transitions from _1_ to root are defined consistently with the other transitions: State L corresponds to the inverse a - 1 of all symbols a ~ E. Because a - la - e, we can set g(• a) = root as root corresponds to e.) Following [-9] we call fir) the suffix link of state r. The suffix links are utilized during the construction of a suffix tree; they also have many uses in the applications (e.g., [11] and [,12]). Automaton STrie(T) is identical to the Aho-Corasick string matching automaton ['1] for the key-word set {Till < i _ n + 1} (the suffix links are called failure transitions in [1]).