Suffix Trie and Suffix Tree
Context: FIT2004_MOC · a Trie of the suffixes of one string instead of words — every substring becomes a root path · PT-03’s drawing-and-counting subject Parent Framework: Trie
Quick Revision
- 🎯 Objective: insert every suffix of
S$➔ substring prefix of a suffix, so substring queries are trie walks; compress every one-child chain into one[start,end]edge ➔ nodes.- 📦 Core Components: Trie of suffixes ➔ worst space | suffix tree ➔ leaves, every internal node branches, nodes, space only with index labels | both to build naively.
- ⚡ Key Constraint: PT-03 counts die on three omissions — the root is a node,
$alone is a suffix, every suffix ends in$. The lecture’s ownappleslides drop the lone$.
📝 How It Works
1. The Suffix Trie — Same Trie, Different Input
- Input ➔
S$of length has suffixes:apple$, pple$, ple$, le$, e$, $. Insert each exactly as a word ➔ Trie §2. - Substring prefix of a suffix (equivalently suffix of a prefix) ➔ every substring is spelled by exactly one root path, and two equal substrings end at the same node.
- Substring search ➔ walk the pattern from the root, no
$at the end ⟹ . - Occurrences of a substring ➔ number of leaves below its node (each leaf is one suffix that starts with it).
- Longest repeated substring ➔ the deepest node with children (two suffixes share it as a prefix).
banana➔ana. - Build ➔ suffixes, longest characters ⟹ time; space nodes worst case.
2. PT-03 Counting Formulas
(no $) · number of distinct non-empty substrings of · inner node node with children (root included) · height edges root ➔ deepest leaf.
| Quantity | Suffix trie | Suffix tree | Why |
|---|---|---|---|
| Leaves | one $-leaf per suffix; compression never merges leaves | ||
| Inner nodes | (all distinct) … (all same) | trie: root one node per distinct substring of · tree: root branching nodes | |
| Total nodes | … | add the two rows above | |
| Height | … | trie: the whole of S$ · tree: count compressed edges | |
| edge lengths | compression keeps every character, only drops nodes |
- Distinct substrings of ➔ trie: nodes root the
$nodes · tree: . Counting all non-root nodes gives the distinct substrings ofS$instead —ABCD$gives , of which belong toABCD. - The extremes flip between the two structures ➔ the suffix trie is smallest on
aaaa( ⟹ nodes) and largest on all-distinct characters ( ⟹ ); the suffix tree is smallest on all-distinct ( nodes, height ) and largest onaaaa( nodes, height ).
3. The Suffix Tree — Compress, Then Store Indices
- Compression ➔ merge every chain of one-child nodes into one edge ⟹ every internal node except possibly the root has children. Exam method: draw the suffix trie, then compress.
- Labels must be
[start,end]indices ➔ storing the substring text on edges keeps space (“we still store the characters all”). Edges are contiguous runs ofS$, so two integers suffice:apple$[0,5],p[1,1],ple$[2,5]. - Prep notation
(i, l)➔ the prep sheet labels the child node with a 1-based start and a length instead of the lecture’s 0-based[start,end]edge:(i, l)[i-1, i+l-2].apple$:p[1,1](2,1),ple$[2,5](3,4). Any occurrence of the text is a valid label; state which convention you use. - nodes ➔ leaves; a tree whose internal nodes all branch has (leaves ) internal nodes ⟹ total. The lecture’s version: ➔ Geometric Series.
- Build time is still ➔ every suffix is still inserted character by character; compression saves space, not time.
- Compression stays only if done carefully ➔ re-slicing strings or copying labels per node reintroduces — the lecture’s “bad practice” warning.
apple$suffix tree — leaf suffix start indexgraph TD R((root)) -->|"apple$ [0,5]"| L0((0)) R -->|"p [1,1]"| P((p)) P -->|"le$ [3,5]"| L2((2)) P -->|"ple$ [2,5]"| L1((1)) R -->|"le$ [3,5]"| L3((3)) R -->|"e$ [4,5]"| L4((4)) R -->|"$ [5,5]"| L5((5))
4. Applied Problems — Think in the Trie, Answer in the Tree
- P3 distinct substrings in ➔ trie answer counts non-
$nodes, ; each tree node absorbs exactly as many trie nodes as its edge has characters ⟹ one traversal summingend-start+1➔ §2. - P4 longest common substring in ➔ build the tree of
s1#s2$(#stops a match straddling both strings).#occurs once ⟹ it never repeats ⟹ it appears only on leaf edges. Leaf containing#⟹ a suffix starting in , else . Post-order: set flags from the leaves upward; answer deepest (by string depth) internal node with both flags. Same shape as longest repeated substring, restricted to cross-string repeats. - P6 fewest substrings of concatenating to , ➔ greedy: walk down the tree of until it cannot extend, cut, restart at the root from the failing character.
ABCCBA,CBAABCA⟹CBAABCA. Why greedy is safe ➔ a suffix of a substring is a substring, so greedy’s -th cut is never left of any solution’s -th cut (stays ahead ➔ Greedy Algorithm). The count is unique, the pieces are not ➔ABBA,ABA:ABAorABA. - P8 shortest unique substring in ➔ unique exactly one leaf below. Shallowest candidate node with a leaf child whose edge is more than just
$; answer length its string depth (an internal node is repeated by definition).cababac⟹ca,ac. - P9 shortest absent string in ➔ shallowest point missing some alphabet character as a continuation ⟹ its string that character. Positions inside an edge have one continuation only.
AAABABBBAover {A,B} ⟹BAA. - P13 suffix-trie size is
[D]➔ a De Bruijn sequence has distinct substrings, one node each ⟹ the bound is tight.
⚖️ Complexity
| Structure | Build (naive) | Space | Substring search | Distinct substrings |
|---|---|---|---|---|
| Suffix trie | best · worst nodes | node count | ||
| Suffix tree, text labels | characters | — | ||
Suffix tree, [start,end] labels | edge-length sum |
📊 Exam Execution Trace & Applied Exercises
Manual Execution Trace — apple$, trie built suffix by suffix, then compressed
| Suffix (start) | Already in trie | New trie nodes | Total nodes | Suffix-tree edge(s) |
|---|---|---|---|---|
apple$ (0) | — | root ➔ [0,5] leaf 0 | ||
pple$ (1) | — | becomes [1,1] ➔ [2,5] leaf 1 | ||
ple$ (2) | p | p now branches ➔ [3,5] leaf 2 | ||
le$ (3) | — | root ➔ [3,5] leaf 3 | ||
e$ (4) | — | root ➔ [4,5] leaf 4 | ||
$ (5) | — | root ➔ [5,5] leaf 5 |
Final Extracted Output: trie nodes, inner, leaves, height · tree nodes, inner (root, p), leaves, height . Check: ⟹ ✓; tree edge lengths ✓.
Applied Exercise — PT-03 counts for banana
Problem: give nodes, inner nodes, leaves and height of the suffix trie and suffix tree of banana, and its distinct substrings.
Final Extracted Output: trie , tree ; edge lengths ⟹ distinct substrings ✓; longest repeated substring deepest inner node ana.
⚠️ Common Mistakes
- 💡 Dropping the lone
$suffix ➔ one leaf short, every count off by one — the lecture’sappleslide shows leaves where there are . - 💡 Routing a suffix into another suffix’s path ➔ creates a cycle — the lecture’s first
appleattempt. Every node has one parent; sharing happens only along a common prefix from the root (ple$reuses the root’spchild, nothing deeper). - 💡 “A suffix tree is space” with text labels ➔ only
[start,end]indices make it . - 💡 “A suffix tree builds in ” ➔ naive insertion is ; linear construction needs Ukkonen, which this unit does not teach.
- 💡 Mixing the two label conventions ➔
(4,4)is start , length (prep, 1-based);[4,4]is one character at 0-based index (lecture). Same digits, different substrings. - 💡 Counting all non-root nodes as distinct substrings ➔ that includes the
$-terminated ones; subtract them.
🧠 Active Recall
Why does compression reduce a suffix trie to nodes, and why does it NOT reduce build time?
Answer
- Short answer: after compression every internal node branches, so internal nodes leaves ⟹ nodes; but each suffix is still inserted character by character ⟹ time.
- Why: Counting ➔ a tree in which every internal node has children has at most leaves internal nodes. Space needs indices too ➔ nodes with -length text labels is still ;
[start,end]makes each edge . Time ➔ insertion walks suffixes of length up to ; only Ukkonen avoids it.
PT-03: which string minimises the suffix trie, and which minimises the suffix tree?
- Hint: drives the trie; repetition drives the tree’s branching.
Answer
- Short answer: the trie is smallest for one repeated character (
aaaa, nodes); the tree is smallest for all-distinct characters (abcd, nodes, height ).- Why: Trie ➔ nodes , and is minimal () when every length has one distinct substring, maximal () when all are distinct. Tree ➔ leaves are fixed at , so only branching nodes vary; all-distinct suffixes share no prefix (root only), while
aaaabranches at everya^k⟹ inner nodes, total.
In P4 (
s1#s2$), why can no internal node's edge contain#, and how does that tell you which string a leaf came from?Answer
- Short answer: an internal node is a substring occurring times;
#occurs once, so any string containing it occurs once ⟹#only appears on leaf edges. A leaf edge containing#is a suffix that starts in .- Why: Branching repetition ➔ two children two different suffixes sharing the path. Leaf test in ➔ the leaf’s suffix starts in its start index , equivalently its edge
[start,end]spans the position of#. Flags propagate up ➔ a node is a substring of (resp. ) iff some leaf below is; one post-order pass sets both flags, then take the deepest node with both.
Prep P2/P3: draw the suffix trees of
ABAABA$andGATTACA$with(i, l)labels, then give nodes / inner / leaves / height.Answer
ABAABA$➔ root ➔A (1,1)➔ {BA (2,2)➔ {ABA$ (4,4),$ (7,1)},$ (7,1),ABA$ (4,4)} ·BA (2,2)➔ {$ (7,1),ABA$ (4,4)} ·$ (7,1)⟹ nodes, inner, leaves, height . Trie for comparison: ⟹ .GATTACA$➔ root ➔$ (8,1)·A (2,1)➔ {$ (8,1),CA$ (6,3),TTACA$ (3,6)} ·CA$ (6,3)·GATTACA$ (1,8)·T (3,1)➔ {ACA$ (5,4),TACA$ (4,5)} ⟹ nodes, inner, leaves, height . Trie: ⟹ .- Why: Branch exactly where two suffixes diverge ➔
ABA$andA$part afterA;ATTACA$,ACA$,A$part afterA. Check ➔ leaves , and ( and ).
🔭 Beyond the lecture (not examinable this semester)
- Suffix array ➔ the suffix start IDs in sorted order: space, substring search by Binary Search in , longest repeated substring by comparing adjacent suffixes. Naive build (merge sort) or (radix); prefix doubling with rank comparison reaches . The 2026 S2 topic page marks the suffix-array lecture not examinable.