作者:20.4 隋春雨
- 概述
后缀树,就是把一串字符的所有后缀保存并且压缩的字典树。相对于字典树来说,后缀树并不是针对大量字符串的,而是针对一个或几个字符串来解决问题。比如字符串的回文子串,两个字符串的最长公共子串等等。
性质:一个字符串构造了一棵树,树中保存了该字符串所有的后缀。
操作:就是建立和应用。
(1)建立后缀树
比如单词banana,它的所有后缀显示到下面的。0代表从第一个字符为起点,终点不用说都是字符串的末尾。
以上面的后缀,我们建立一颗后缀树。如下图,为了方便看到后缀,我没有合并相同的前缀。
前面简介的时候我们说了,后缀树是把一个字符串所有后缀压缩并保存的字典树。所以我们把字符串的所有后缀还是按照字典树的规则建立,就成了下图的样子。这就是后缀树的压缩,可见更加节省空间。
注意还是和字典树一样,根节点必须为空。 - 后缀树的应用
后缀树能解决大多数字符串的问题
(1)查找某个字符串s1是否在另外一个字符串s2中。这个很简单,如果s1在字符串s2中,那么s1必定是s2中某个后缀串的前缀。理解以下后缀串的前缀这个词,其实每个后缀串也就是起始地点不同而已,前缀也就是从开头开始结尾不定。后缀串的前缀就可以组合成该原先字符串的任意子串了。比如banana,anan是anana这个后缀串的前缀。
(2)指定字符串s1在字符串s2中重复的次数
比如说banana是s1,an是s2,那么计算an出现的次数实际上就是看an是几个后缀串的前缀。上图的a节点是保存所有起始为a字母的后缀串,我们看a字母后的n字母的引用计数即可。
(3)两个字符串S1,S2的最长公共部分(广义后缀树)
(4)最长回文串(广义后缀树)
详细介绍见博客从Trie树(字典树)谈到后缀树
后缀树(Suffix tree):
:一个具有m个词的字符串S的后缀树T,就是一个包含一个根节点的有向树,该树恰好带有m个叶子,这些叶子被赋予从1到m的标号。 每一个内部节点,除了根节点以外,都至少有两个子节点,而且每条边都用$的一个非空子串来标识。出自同一节点的任意两条边的标识不会以相同的词开始。后缀树的关键特征是:对于任何叶子i,从根节点到该叶子所经历的边的所有标识串联起来后恰好拼出S的从i位置开始的后缀,即S[i,…,m]。树中节点的标识被定义为从根到该节点的所有边的标识的串联。
后缀:一个长度为m的字符串序列S=s1 s2 ....sm,记Si=si si+1 .....sm为S的第 i 个后缀,显然S1=S 为原来的字符串;
后缀树的定义:长度为m的序列S,其后缀树是一个有向树,满足以下条件:
1)有m个叶结点; 2)除了根结点和叶结点,每一个内部结点至少有两条边(子节点),每条边对应S中的一个非空序列;
3)从任何一个内部结点出发的两条边对应的字符串序列的第一个字符都不相同;
4)从根结点到叶结点的路径上的字符序列构成了S从i开始的一个后缀字符串;
路径标签:一个路径上对应的字符序列称为路径标签;
结点标签:从根结点开始到此结点对应的路径的标签称为结点标签;
并不是所有的字符序列都有后缀树,例如xabxa(其后缀有xabxa,abxa,bxa,xa,a)xa为xabxa的前缀,为了解决此问题,通常在字符串末尾加上$符号,使得任何一个后缀都不为其他后缀的前缀。
(xabxa$的后缀树)
注:从根结点到叶子结点路径上的字符(词)表示对应叶节点 i 开始的某一个后缀字符串,叶子结点存储了起始位置 i 有几个字符(词)就有几个叶子结点,根结点和内部结点不存储任何数据,只有叶子结点和边存储数据;
隐含后缀树:序列S的隐含后缀树是指,序列S$的后缀树,去掉那些有$的边的$符号,之后将空边删除所得到的树;
(xabxa$的隐含后缀树)
即:隐含后缀树构造即删除所有边的$符号之后将空边删除;
后缀树的构造:
根据S字符串的前缀s1 s2....si构造隐含后缀树Ti,当i=m时即可构造出S的隐含后缀树;
后缀树伪代码
1、初始化一个隐含后缀树
for i 从 1 到 m: //m为字符串长度
逐步构造隐含后缀树
for j 从 1 到 i+1:
#后缀拓展规则
在已经构建好的后缀树中找到从root结点出发
标记位S[j....i]的序列,如果需要的话将
S[i+1]加入到这条路径后面
进行下一次隐含后缀树的构造
end //完成整个后缀树的构造
后缀的扩展规则:
令β=S【j....i】,这里的β为S【1....i】的某一个后缀,第j步扩展是为了保证序列β,S【i+1】在树中;
1、规则1:路径β是以一个叶子结点结尾的只需将S【i+1】直接加入在叶子结点上面;
(路径β以一个叶子结点为结束,直接在此路径上增加一个S【i+1】)
2、规则2:路径β后面没有以S【i+1】开始,但至少有一个路径是β的延续,如果β终止于内部结点则新建一个叶子结点
作为这个内结点的子结点并将此边标记为S【i+1】,当β是在一条边的中间时除了建立一个子结点外还要建立一个从根结点
出发标记为β的内部结点。
(路径β在中间部分则从其末尾插入一个内部结点以及生成这个内部结点的子结点其路径标记为S【i+1】)
3、规则3:有某个路径是β的末端开始且S【i+1】开头则无需进行任何操作;
(有以β开头的路径什么也不做)
例子:字符串S=“axabxb“的后缀树的逐步构建;
字符 a x a b x b
下标 1 2 3 4 5 6
时间复杂度分析:
对于内层循环每一次都要寻找一条路径β,其时间复杂度为O(β)内外层循环为O(i^2),合起来时间复杂度为O(m^3);