跳转到内容

Suffix Tree

快速概览

如果说 Trie 是为了「找一批模式」而设计的,那么 Suffix Tree 就是为了「在一个文本中找任何子串」而设计的。它通过压缩所有后缀的共享前缀,将 $O(n^2)$ 的 Suffix Trie 降至 $O(n)$ 空间。

  • 理解从 Suffix Trie 到 Suffix Tree 的压缩演化过程
  • 掌握如何利用 Suffix Tree 解决最长重复子串与长公共子串问题
  • 建立 Suffix Tree、Suffix Array 与 FM-index 之间的技术连贯性
  • 理解其在基因组自比对和结构域分析中的应用
所属板块 核心方法

索引、比对、组装与概率模型构成的核心算法主轴。

阅读目标 帮助建立阅读上下文

先判断这页与你当前问题的关系,再决定是否深入展开。

建议前置 先建立相关基础对象与方法直觉

建议先建立相关基础对象与方法直觉,再进入本页。

Suffix Tree的概念由 Weiner 于1973年首次提出,其构建算法可以在 O(n)O(n) 时间内完成。1976年,McCreight 提出了一个更易于理解的线性时间构建算法。1995年,Ukkonen 提出了一个在线的线性时间构建算法,进一步简化了实现。

这三篇论文构成了 Suffix Tree 理论的基础,Gusfield 在 1997 年出版的 Algorithms on Strings, Trees, and Sequences 一书中对 Suffix Tree 及其在计算生物学中的应用进行了系统总结。

Suffix Trie(后缀字典树)
将文本 $T$ 的所有 $n$ 个后缀逐一插入 Trie 得到的结构。节点数为 $O(n^2)$,空间开销过大。
Suffix Tree(后缀树)
对 Suffix Trie 进行路径压缩后的结果。每个内部节点至少有两个子节点,节点数为 $O(n)$。
广义后缀树(Generalized Suffix Tree)
将多段序列的所有后缀插入同一棵 Suffix Tree,用于解决跨序列问题(如最长公共子串)。

将文本 TT 的所有后缀直接插入 Trie,得到 Suffix Trie

  • 节点数:O(n2)O(n^2)(每个后缀贡献最多 nn 个节点)
  • 空间开销过大,不实用

示例BANANA$ 的所有后缀

  • BANANA$
  • ANANA$
  • NANA$
  • ANA$
  • NA$
  • A$
  • $

观察:Suffix Trie 中存在大量只有一个子节点的内部节点(线性链)。

压缩策略:将线性路径上的节点合并,边上标记子串而非单个字符。

压缩后

  • 节点数:O(n)O(n)(最多 2n12n-1 个节点,其中 nn 个叶节点和至多 n1n-1 个内部节点)
  • 边标签:子串区间 [i,j][i,j] 而非完整字符串

观察 Suffix Trie,你会发现许多路径是”单传”的(一个节点只有一个子节点)。 Suffix Tree 的核心改进是:将所有非分叉的路径压缩为一条边

  • 边上的标签不再是单个字符,而是文本中的一个子串区间 [i,j][i, j]

T = \text{BANANA\}为例( 为例(`` 是哨兵字符,字典序最小):

所有后缀按字典序排列:

  1. $
  2. A$
  3. ANA$
  4. ANANA$
  5. NA$
  6. NANA$
  7. BANANA$

压缩后的 Suffix Tree 结构:

(root)
/ | \
$ A B
| / \ \
($$) N | ANANA$
/ \ NA$
| \
ANA$ NA$
/ \
ANANA$ A$
  • 根节点有 3 条出边,分别以 $AB 开头。
  • A 后面分叉为 NA$NANA$ 两条路径,以及 ANA$ 的共享前缀路径。
  • 每个叶节点对应一个后缀,用其在文本中的起始位置标记。

一旦构建好 Suffix Tree,许多复杂的字符串问题都能在极短时间内解决:

给定查询模式 PP(长度 mm),判断 PP 是否为 TT 的子串:

  • 从根出发,沿 PP 的字符逐层向下走。
  • 如果成功走完 mm 步(或到达某条边的中间),则 PPTT 的子串。
  • 时间O(m)O(m)
  • 直觉:树中任何一个内部节点(Internal Node)都代表了一个至少出现两次的子串(因为它有至少两个分叉,对应至少两个不同的后缀共享此前缀)。
  • 算法:找到树中”最深”的内部节点(即对应的路径标签最长)。
  • 时间O(n)O(n)(构建后一次遍历)。

最长公共子串(Longest Common Substring)

Section titled “最长公共子串(Longest Common Substring)”
  • 场景:寻找序列 S1S_1S2S_2 共有最长子串。
  • 算法:构建一棵包含 S1#S_1\# 和 $S_2$$ 的广义后缀树(Generalized Suffix Tree)。标记每个叶节点属于哪个序列。找到最深的、同时包含两个序列叶子节点的内部节点。
  • 时间O(S1+S2)O(|S_1| + |S_2|)

T = \text{BANANA\}$ 中寻找最长重复子串:

遍历内部节点,找到最深的一个:

  • 节点 ANA(路径标签长度 3):对应后缀 ANA$(位置 3)和 ANANA$(位置 1),出现两次。
  • 节点 NA(路径标签长度 2):对应后缀 NA$(位置 4)和 NANA$(位置 2),出现两次。
  • 节点 A(路径标签长度 1):对应多个后缀。

最长重复子串为 ANA,长度 3。

Suffix Tree 是许多经典算法教材的核心,因为它提供了处理长序列最直观的几何解释:

  • 全基因组自比对:寻找基因组内部的重复元件(如转座子、LINE、SINE)。通过最长重复子串,可以快速定位大型重复区域。
  • 结构域发现:通过多序列的最长公共子串寻找保守的蛋白质结构域。
  • MUMmer:著名的全基因组比对工具,其核心即基于 Suffix Tree 来寻找最大唯一匹配(Maximal Unique Matches, MUMs)。
  • 基因预测辅助:通过分析编码区与非编码区的 k-mer 分布差异,辅助基因结构预测。

MUMmer 是基于 Suffix Tree 的全基因组比对工具,其核心流程为:

  1. 为参考基因组构建 Suffix Tree。
  2. 将查询基因组的每个位置在 Suffix Tree 中查找,寻找最大唯一匹配(即在两个基因组中都只出现一次的最长公共子串)。
  3. 对 MUMs 进行排序,用最短提升超几何(Sparse Superstring) 算法将它们组装为对齐块。
指标Suffix TrieSuffix Tree
节点数O(n2)O(n^2)O(n)O(n)2n\leq 2n
边标签总长度O(n2)O(n^2)O(n)O(n)(用区间表示)
构建时间O(n2)O(n^2)O(n)O(n)(Ukkonen 算法)
子串查询O(m)O(m)O(m)O(m)
最长重复子串O(n2)O(n^2)O(n)O(n)
最长公共子串O(n12+n22)O(n_1^2 + n_2^2)O(n1+n2)O(n_1 + n_2)
  • 文本静态:Suffix Tree 的构建代价为 O(n)O(n),当文本固定后可以进行任意多次查询,摊还后每次查询的成本极低。
  • 内存充足:尽管 Suffix Tree 的节点数为 O(n)O(n),但每个节点需要存储指向子节点的指针。对于人类基因组(n3×109n \approx 3 \times 10^9),即使每个节点只存 2 个指针,也需要约 244824\text{--}48 GB 内存。这在实践中往往不可行。
  • 需要复杂查询:如果只需要精确子串匹配,FM-index 更节省空间。Suffix Tree 的优势在于支持最长重复子串、公共子串等复杂查询。

虽然 Suffix Tree 功能强大,但在现代超大规模数据处理中,它的指针开销仍然较大。这促使了更紧凑结构的诞生:

  1. Suffix Array (后缀数组):将后缀按字典序排列,仅存索引,空间更省。查询时间 O(mlogn)O(m \log n),配合 LCP 数组可以模拟 Suffix Tree 的大部分功能。
  2. BWT / FM-index:基于 Burrows-Wheeler 变换,实现了压缩与查询的完美结合,是 BWA 和 Bowtie 的基石。
Suffix Tree (功能最强,空间最大)
↓ 去除指针,保留后缀排序
Suffix Array (中等空间,查询稍慢)
↓ 添加 BWT 变换 + 波浪树
FM-index (空间最小,查询 $O(m)$)

Suffix Tree 是理论分析的首选工具(直观、功能完整),Suffix Array 是实现折中(平衡空间与速度),FM-index 是工业标准(极致压缩)。