后缀树
后缀树Suffix-Tree的应用
关于字符串的处理,如最长公共子串、回文问题等,用后缀树可以很好的解决,下面对其应用做一个简单的介绍。
什么是后缀树
后缀树(Suffix tree)是一种树形数据结构,能快速解决很多关于字符串的问题。后缀樹的概念最早由Weiner 於1973年提出,既而由McCreight 在1976年和Ukkonen在1992年和1995年加以改進完善。
总结起来,它主要可以解决类似如下的一些问题:
- 查找字符串o是否在字符串S中
- 指定字符串T在字符串S中的重复次数
- 字符串S中的最长重复子串
- 两个字符串S1,S2的最长公共部分
这些问题如何解决,我们最后再谈。
结构定义
假设我们现在有一个字符串T = "mississippi",那么我们可以列出它所有的后缀字符串:
1
2
3
4
5
6
7
8
9
10
11
12
|
T1=mississippi
T2=ississippi
T3=ssissippi
T4=sissippi
T5=issippi
T6=ssippi
T7=sippi
T8=ippi
T9=ppi
T10=pi
T11=i
T12=(empty)
|
然后对所有的非空后缀做排序,可以得到如下字符串组:
1
2
3
4
5
6
7
8
9
10
11
|
T11=i
T8 =ippi
T5 =issippi
T2 =ississippi
T1 =mississippi
T10=pi
T9 =ppi
T7 =sippi
T4 =sissippi
T6 =ssippi
T3 =ssissippi
|
可以看出很明显的树形规律,我们可以把这棵树画出来:
其中每个叶子节点都可以引出一个字符串,图中一共十个字符串(缺少空串和i字串,为了解决此问题,通常我们会在字符串的末尾加上一个$符号,这样构造后缀树的时候就不会出现少串的现象了)
上图中,每个叶子节点在原字符串的起始位置也可以标出来,即:
后缀树的应用
现在再来看看本文开头提到的四个问题,对照上面的图,我们很容易可以获得他们的解法:
- 查找字符串o是否在字符串S中
解法:如果S存在于o中,那么S必然是o的某一个后缀的前缀,按照Trie树搜索前缀的方法,遍历后缀树即可。复杂度为O(M),其中M为字符串S的长度。 - 指定字符串T在字符串S中的重复次数
解法:在字符串S后追加$构造包含所有后缀的完整后缀树,在其中找到T子川,的最后一个节点,该节点拥有的叶子节点个数几位重复次数,复杂度为O(M),M为T的长度。 - 字符串S中的最长重复子串
解法:遍历整个后缀树,找到深度最大的非叶子节点,复杂度为O(N),N为字符串的长度。 - 两个字符串S1,S2的最长公共部分
解法:分别 为S1、S2追加#、$作为末尾,把他们压入同一个后缀树,然后找到最深的非叶子节点,该节点的叶子节点中,既有#又有$。复杂度为构造两颗后缀树的复杂度之和,取最大即可max(O(N),O(M)),其中N、M为S1、S2的长度,假设我们以线性时间构造了后缀树,下位讲解构造方法。
最初的构造方法(O(N^2))
按照上文的图形,我们可以想到一个自然而然的方法构造后缀树,即遍历字符串,取得每一个后缀,然后再遍历这个后缀,依次添加到后缀树中,这种做法由于要遍历两次,复杂度接近于O(N^2),其过程为(假设有字符串S = papua):
改进的构造方法
1995年Ukkonen发明了新的构造算法,可以把后缀树的构造时间复杂度控制在线性时间内。
后缀树跟后缀Trie有着一样的布局, 但它把只有一个儿子的节点给剔除了. 这个过程被称为路径压缩, 这意味着树上的某些边将表示一个序列而不是单独的字符。如上面的papua的后缀树中,从根节点引出的ua,其实是由两个节点u、a构成的,但是因为u只有一个儿子节点,所以把他们合并,以压缩存储空间。
McCreight最初的构造法是有些缺陷的, 原则上它要按逆序构造, 也就是说字符要从末端开始插入. 如此一来, 便不能作为在线算法, 它变得更加难以应用于实际问题, 如数据压缩。而且遍历插入后缀树也需要N^2两级的事件。
Ukkonen把原算法作了一些改动, 把它变成了从左往右。对于所给的文本T, Esko Ukkonen的算法是由一棵空树开始, 逐步构造T的每个前缀的后缀树. 比如我们构造BANANAS的后缀树, 先由B开始, 接着是BA, 然后BAN, … . 不断更新直到构造出BANANAS的后缀树。如图:
因此,每次把一个原字符串的前缀追加到后缀树中,相当于更新该后缀树,如在BA后加入BAN,相当于只是新增了N字符在末尾,依次类推,我们主要关注后缀树的更新即可。
具体的更新过程比较复杂,可以参考下面的文章了解。
Suffix tree—后缀树
l 简介
后缀树是一种PAT树,它描述了给定字符串的所有后缀,许多重要的字符串操作都能够在后缀树上快速地实现。
l 定义
一个长度为n的字符串S,它的后缀树定义为一棵满足如下条件的树:
n 从根到树叶的路径与S的后缀一一对应。即每条路径惟一代表了S的一个后缀;
n 每条边都代表一个非空的字符串;
n 所有内部节点(根节点除外)都有至少两个子节点。
由于并非所有的字符串都存在这样的树,因此S通常使用一个终止符号进行填充(通常使用$)。
l 优点
n 匹配快。对于长度为m的模式串,只需花费至多O(m)的时间进行匹配。
n 空间省。Suffix tree的空间耗费要低于Suffix trie,因为Suffix tree除根节点外不允许其内部节点只含单个子节点,因此它是Suffix trie的压缩表示。