๐ง Suffix Tree Fundamentals
๐ฏ What is a Suffix Tree?
A suffix tree is a compressed trie containing all the suffixes of a given string. It allows efficient pattern matching, substring queries, and many string operations in linear time. For a string of length n, a suffix tree can be built in O(n) time using Ukkonen's algorithm and uses O(n) space. Suffix trees are fundamental to bioinformatics, especially for genome indexing and read alignment.
Why Suffix Trees Matter
Linear time: Build and query. Pattern matching: O(m) search. Genome indexing: Fast lookup. Read alignment: BWA, Bowtie. Repeat detection: Find repeats. Compression: BWT.
# Suffix Tree & Genome Suffix โ the big picture
# Indexing genomes for fast search.
print("""
=== Suffix Tree & Genome Suffix ===
What it is:
- Compressed trie of all suffixes
- Linear-time construction
- Linear-time queries
- Fundamental string data structure
Why it matters:
- Pattern matching
- Genome indexing
- Read alignment
- Repeat detection
- Sequence assembly
- Compression
Key structures:
- Trie
- Suffix tree
- Suffix array
- Burrows-Wheeler Transform (BWT)
- FM-index
Applications:
- BWA, Bowtie
- Genome browsers
- RepeatMasker
- MUMmer
- Sequence alignment
- String matching
Genome suffix:
- Suffix tree of genome
- Index for alignment
- Memory efficient
- Fast lookup
""")
print("=== Historical Context ===")
print("""
1973: Weiner โ first suffix tree (O(n) space)
1976: McCreight โ improved construction
1995: Ukkonen โ online linear-time construction
1990s: Suffix arrays (Manber & Myers)
1994: Burrows-Wheeler Transform
2000: FM-index (Ferragina & Manzini)
2009: BWA (Li & Durbin)
2009: Bowtie (Langmead et al.)
2020: Graph genomes, pangenomes
2025: AI, compressed indexes
""")
print("=== Why It's Hard ===")
print("""
1. SPACE
- Naive suffix tree: O(n^2)
- Need compression
- Ukkonen: O(n)
2. CONSTRUCTION
- Complex algorithm
- Suffix links
- Online
3. MEMORY
- Large genomes
- 3 Gb human genome
- Compressed indexes
4. ALIGNMENT
- Mismatches
- Indels
- Paired-end
5. SCALE
- Billions of reads
- Fast alignment
- Indexing
""")๐ฏ Why Suffix Trees Matter
๐ฏ The Value of Suffix Trees
Suffix trees are one of the most powerful data structures in string algorithms. They enable linear-time solutions to many problems that would otherwise be quadratic.
Key Applications
Pattern matching: O(m). Genome indexing: Fast. Read alignment: BWA/Bowtie. Repeat detection: Repeats. Compression: BWT. Bioinformatics: Core.
# Why Suffix Trees Matter
print("=== 1. Pattern Matching ===")
print("""
Pattern matching:
- Search pattern P in text T
- Suffix tree: O(m) time
- Build: O(n)
- Total: O(n + m)
Example:
T = "banana$"
P = "ana"
โ Find all occurrences in O(m)
""")
print("\n=== 2. Genome Indexing ===")
print("""
Genome indexing:
- Index genome once
- Query many times
- Fast lookup
Example:
Human genome (3 Gb)
โ Suffix tree / FM-index
โ Align millions of reads
""")
print("\n=== 3. Read Alignment ===")
print("""
Read alignment:
- Short reads (50-300 bp)
- Align to genome
- Millions of reads
Tools:
- BWA (BWT + FM-index)
- Bowtie (FM-index)
- SOAP (suffix array)
Example:
Illumina reads
โ BWA
โ Aligned BAM
""")
print("\n=== 4. Repeat Detection ===")
print("""
Repeat detection:
- Find repeated sequences
- Transposons
- Tandem repeats
Example:
Genome โ suffix tree
โ Find repeats
โ RepeatMasker
""")
print("\n=== 5. Compression ===")
print("""
Compression:
- BWT
- FM-index
- Compressed genomes
Example:
BWT of genome
โ Compressed
โ FM-index
""")
print("\n=== 6. Sequence Assembly ===")
print("""
Assembly:
- Overlap detection
- Contig assembly
- De Bruijn graphs
Example:
Reads โ suffix tree
โ Overlaps
โ Contigs
""")
print("\n=== 7. Comparative Genomics ===")
print("""
Comparative:
- MUMmer
- Synteny
- Alignment
Example:
Two genomes
โ Suffix tree
โ MUMs
โ Alignment
""")
print("\n=== 8. SNP Discovery ===")
print("""
SNP discovery:
- Align reads
- Call variants
- Suffix tree
Example:
Reads โ BWA
โ Variants
โ SNPs
""")
print("\n=== 9. Metagenomics ===")
print("""
Metagenomics:
- Many genomes
- Index
- Classify reads
Example:
Metagenome
โ Index
โ Classification
""")
print("\n=== 10. Pangenomics ===")
print("""
Pangenomics:
- Multiple genomes
- Graph
- Index
Example:
Pangenome
โ Graph
โ FM-index
โ Alignment
""")๐ค Trie to Suffix Tree
๐ฏ From Trie to Suffix Tree
A trie (prefix tree) stores strings by their prefixes. A suffix tree is a compressed trie of all suffixes of a string. Compression reduces space from O(nยฒ) to O(n).
Trie to Suffix Tree
Trie: Prefix tree. Suffix trie: All suffixes. Compression: Merge paths. Suffix tree: O(n) space. Edges: Labels. Leaves: Suffixes.
# Trie to Suffix Tree
print("=== 1. Trie ===")
print("""
Trie:
- Prefix tree
- Each node = prefix
- Edges = characters
- Path = string
Example:
Insert "cat", "car", "dog"
root
/ | \\
c d ...
| |
a o
/ \\ \\
t r g
"cat" and "car" share "ca"
""")
print("\n=== 2. Suffix Trie ===")
print("""
Suffix trie:
- Trie of all suffixes
- Insert all suffixes
- Each leaf = suffix
Example:
S = "banana$"
Suffixes:
banana$
anana$
nana$
ana$
na$
a$
$
Suffix trie:
root
โโโ b โ a โ n โ a โ n โ a โ $
โโโ a โ n โ a โ n โ a โ $
โ โโโ $ (wait, a$ is also a suffix)
โโโ n โ a โ n โ a โ $
โโโ a โ n โ a โ $
โโโ n โ a โ $
โโโ a โ $
โโโ $
""")
print("\n=== 3. Suffix Tree (Compressed) ===")
print("""
Suffix tree:
- Compress unary paths
- O(n) nodes
- O(n) space
Example:
S = "banana$"
Suffix tree:
root
โโโ "banana$" โ 1
โโโ "a"
โ โโโ "na"
โ โ โโโ "na$" โ 3
โ โ โโโ "$" โ 5
โ โโโ "$" โ 6
โโโ "n"
โ โโโ "ana$" โ 4
โ โโโ "a$" โ 2
โโโ "$" โ 7
Note: Edges can be multiple characters.
""")
print("\n=== 4. Properties ===")
print("""
Properties:
- n leaves (one per suffix)
- n-1 internal nodes
- Total: 2n-1 nodes
- O(n) space
- Build: O(n) with Ukkonen
Terminator:
- $ (unique character)
- Ensures no suffix is prefix of another
""")
print("\n=== 5. Edge Labels ===")
print("""
Edge labels:
- Substring of S
- Stored as (start, end) pair
- O(1) space per edge
Example:
Edge "banana$"
โ (0, 6) in S
โ O(1) space
This is key to O(n) space.
""")
print("\n=== 6. Suffix Links ===")
print("""
Suffix links:
- From node v to node w
- w represents suffix of v's path
- Key to Ukkonen's algorithm
Example:
Node "banana"
โ Suffix link โ "anana"
โ Suffix link โ "nana"
...
Used in construction.
""")
print("\n=== 7. Node Depth ===")
print("""
Depth:
- String depth
- Path length
- Longest common prefix
Example:
Node "ana"
โ Depth 3
โ Path length 3
""")
print("\n=== 8. Longest Common Prefix ===")
print("""
LCP:
- Longest common prefix
- Between suffixes
- LCP array
Example:
Suffixes:
"ana$"
"anana$"
LCP: "ana" = 3
""")
print("\n=== 9. Applications ===")
print("""
Applications:
- Pattern matching
- Longest repeated substring
- Longest common substring
- Longest palindrome
- Suffix array
- BWT
""")
print("\n=== 10. Example ===")
print("""
S = "banana$"
Suffixes:
0: banana$
1: anana$
2: nana$
3: ana$
4: na$
5: a$
6: $
Suffix tree:
- Root
- Edges labeled
- Leaves = suffixes
- Internal nodes = LCPs
""")โ๏ธ Construction (Ukkonen)
๐ฏ Ukkonen's Algorithm
Ukkonen's algorithm builds a suffix tree in O(n) time by adding characters one at a time. It uses suffix links and implicit suffixes to achieve linear time.
Ukkonen
Online: Character by character. Implicit: Suffixes. Suffix links: Key. Active point: State. O(n): Linear. Complex: But elegant.
# Ukkonen's Algorithm
print("=== Overview ===")
print("""
Ukkonen:
- Online construction
- Add characters one by one
- O(n) time
- O(n) space
Key ideas:
- Implicit suffixes
- Suffix links
- Active point
- Rule 1, 2, 3
""")
print("\n=== Phases ===")
print("""
Phase i:
- Add character S[i]
- Update tree
- n phases total
Example:
S = "banana$"
Phase 1: add 'b'
Phase 2: add 'a'
...
Phase 7: add '$'
""")
print("\n=== Extensions ===")
print("""
Extension j:
- In phase i
- Add S[i] to suffix j
- i extensions per phase
Total:
- O(n^2) naive
- Ukkonen: O(n) with tricks
""")
print("\n=== Rules ===")
print("""
Rule 1:
- Path ends at leaf
- Extend leaf
- No new node
Rule 2:
- Path ends mid-edge
- Split edge
- Create new leaf
Rule 3:
- Path ends at node
- Do nothing
- Implicit suffix
""")
print("\n=== Suffix Links ===")
print("""
Suffix links:
- From node v to node w
- w = suffix of v's path
- Key optimization
Example:
Node "banana"
โ Suffix link โ "anana"
Used to skip work.
""")
print("\n=== Active Point ===")
print("""
Active point:
- (active_node, active_edge, active_length)
- Where to add next
- Avoids rescanning
Example:
After phase i:
- Active node
- Active edge
- Active length
""")
print("\n=== Complexity ===")
print("""
Complexity:
- Time: O(n)
- Space: O(n)
- Nodes: 2n-1
- Edges: 2n-2
Proof:
- Each extension O(1) amortized
- Total O(n)
""")
print("\n=== Pseudocode ===")
print("""
Ukkonen(S):
root = Node()
active_node = root
active_edge = None
active_length = 0
remaining = 0
for i in range(len(S)):
remaining += 1
last_new_node = None
while remaining > 0:
if active_length == 0:
active_edge = S[i]
if active_edge not in active_node.children:
# Rule 2: create leaf
leaf = Node()
active_node.children[active_edge] = leaf
if last_new_node:
last_new_node.suffix_link = active_node
last_new_node = None
else:
# Edge exists
next_node = active_node.children[active_edge]
edge_length = next_node.end - next_node.start
if active_length >= edge_length:
# Move down
active_edge += S[i - active_length + edge_length]
active_length -= edge_length
active_node = next_node
continue
if S[next_node.start + active_length] == S[i]:
# Rule 3: implicit
if last_new_node and active_node != root:
last_new_node.suffix_link = active_node
last_new_node = None
active_length += 1
break
# Rule 2: split edge
split = Node()
split.start = next_node.start
split.end = next_node.start + active_length
active_node.children[active_edge] = split
split.children[S[i]] = Node()
next_node.start += active_length
split.children[S[next_node.start]] = next_node
if last_new_node:
last_new_node.suffix_link = split
last_new_node = split
remaining -= 1
if active_node == root and active_length > 0:
active_length -= 1
active_edge = S[i - remaining + 1]
elif active_node != root:
active_node = active_node.suffix_link
""")
print("\n=== Example ===")
print("""
S = "banana$"
Build step by step:
b โ ba โ ban โ bana โ banan โ banana โ banana$
Each phase adds a character.
""")๐ Suffix Array
๐ฏ Sorted Suffixes
A suffix array is a sorted array of all suffixes of a string. It uses less memory than a suffix tree and is often faster in practice.
Suffix Array
Sorted: Suffixes. Space: O(n). Build: O(n). LCP: Longest common prefix. Search: Binary search. FM-index: Basis.
# Suffix Array
print("=== Definition ===")
print("""
Suffix array:
- Array of indices
- Sorted suffixes
- SA[i] = start of i-th suffix
Example:
S = "banana$"
Suffixes:
0: banana$
1: anana$
2: nana$
3: ana$
4: na$
5: a$
6: $
Sorted:
6: $
5: a$
3: ana$
1: anana$
4: na$
2: nana$
0: banana$
SA = [6, 5, 3, 1, 4, 2, 0]
""")
print("\n=== Construction ===")
print("""
Construction:
- Naive: O(n^2 log n)
- Manber-Myers: O(n log n)
- SA-IS: O(n)
- DC3: O(n)
Practical:
- SA-IS (linear)
- DC3 (linear)
""")
print("\n=== LCP Array ===")
print("""
LCP:
- Longest common prefix
- Between adjacent suffixes
- LCP[i] = LCP(SA[i], SA[i-1])
Example:
SA = [6, 5, 3, 1, 4, 2, 0]
LCP = [0, 0, 1, 3, 0, 2, 0]
LCP[3] = 3
โ "ana" between "ana$" and "anana$"
""")
print("\n=== Pattern Search ===")
print("""
Search:
- Binary search
- O(m log n)
- Or O(m + log n) with LCP
Example:
Search "ana"
โ SA = [6, 5, 3, 1, 4, 2, 0]
โ Binary search
โ Find index 2 (SA[2] = 3)
""")
print("\n=== Space ===")
print("""
Space:
- SA: n integers
- LCP: n integers
- Total: 2n integers
Example:
Human genome: 3 Gb
โ 3 billion integers
โ ~12 GB (32-bit)
โ ~24 GB (64-bit)
""")
print("\n=== FM-index ===")
print("""
FM-index:
- Based on BWT
- BWT from SA
- Compressed
- Fast search
BWT:
BWT[i] = S[SA[i] - 1]
Example:
SA = [6, 5, 3, 1, 4, 2, 0]
BWT = [$, a, n, a, n, a, b]
""")
print("\n=== Python Implementation ===")
print("""
def build_suffix_array(s):
\"\"\"Build suffix array naively.\"\"\"
n = len(s)
suffixes = [(s[i:], i) for i in range(n)]
suffixes.sort()
return [i for _, i in suffixes]
# Example
s = "banana$"
sa = build_suffix_array(s)
print(sa)
# Output: [6, 5, 3, 1, 4, 2, 0]
""")
print("\n=== Efficient Construction ===")
print("""
def build_sa_is(s):
\"\"\"SA-IS (simplified).\"\"\"
# Full SA-IS is complex
# Use sorted() for demo
return sorted(range(len(s)), key=lambda i: s[i:])
# For production, use:
# - pydivsufsort
# - libdivsufsort
# - SA-IS implementation
""")
print("\n=== LCP Array ===")
print("""
def build_lcp(s, sa):
\"\"\"Build LCP array (Kasai).\"\"\"
n = len(s)
rank = [0] * n
for i, idx in enumerate(sa):
rank[idx] = i
lcp = [0] * n
k = 0
for i in range(n):
if rank[i] == n - 1:
k = 0
continue
j = sa[rank[i] + 1]
while i + k < n and j + k < n and s[i+k] == s[j+k]:
k += 1
lcp[rank[i]] = k
if k > 0:
k -= 1
return lcp
""")
print("\n=== Use Cases ===")
print("""
Use cases:
- Pattern matching
- Genome indexing
- BWT
- FM-index
- Read alignment
- Repeat detection
- Longest common substring
""")๐ Burrows-Wheeler Transform
๐ฏ BWT
The Burrows-Wheeler Transform (BWT) is a reversible string transformation that rearranges characters to improve compression. It's the basis of the FM-index.
BWT
Reversible: Invertible. Rearranges: Characters. Compression: Better. FM-index: Basis. Alignment: BWA/Bowtie. Efficient: O(n).
# Burrows-Wheeler Transform
print("=== BWT Definition ===")
print("""
BWT:
- Reversible transformation
- Rearranges characters
- Clusters similar characters
- Improves compression
Steps:
1. Form all rotations
2. Sort rotations
3. Take last column
""")
print("\n=== Example ===")
print("""
S = "banana$"
Rotations:
banana$
anana$b
nana$ba
ana$ban
na$bana
a$banan
$banana
Sorted:
$banana
a$banan
ana$ban
anana$b
banana$
na$bana
nana$ba
Last column:
a
n
a
b
$
a
n
BWT = "anab$an"
""")
print("\n=== BWT from Suffix Array ===")
print("""
BWT[i] = S[SA[i] - 1]
Example:
S = "banana$"
SA = [6, 5, 3, 1, 4, 2, 0]
BWT[0] = S[5] = 'a' (SA[0]=6, 6-1=5)
BWT[1] = S[4] = 'n' (SA[1]=5, 5-1=4)
BWT[2] = S[2] = 'n' (SA[2]=3, 3-1=2)
BWT[3] = S[0] = 'b' (SA[3]=1, 1-1=0)
BWT[4] = S[3] = 'a' (SA[4]=4, 4-1=3)
BWT[5] = S[1] = 'a' (SA[5]=2, 2-1=1)
BWT[6] = S[6] = '$' (SA[6]=0, 0-1=-1 โ 6)
BWT = "annbaa$"
""")
print("\n=== Inverse BWT ===")
print("""
Inverse:
- Reconstruct original
- Using LF mapping
- O(n)
Steps:
1. Create F (first column)
2. Compute LF mapping
3. Reconstruct
""")
print("\n=== LF Mapping ===")
print("""
LF:
- Last-to-first
- BWT[i] โ F
- Rank
Example:
BWT = "annbaa$"
F = "$aaabnn"
LF[i] = rank of BWT[i] in F
""")
print("\n=== Python BWT ===")
print("""
def bwt(s):
\"\"\"Burrows-Wheeler Transform.\"\"\"
n = len(s)
rotations = [s[i:] + s[:i] for i in range(n)]
rotations.sort()
return ''.join(r[-1] for r in rotations)
def inverse_bwt(bwt):
\"\"\"Inverse BWT.\"\"\"
n = len(bwt)
table = [''] * n
for _ in range(n):
table = sorted(bwt[i] + table[i] for i in range(n))
for row in table:
if row.endswith('$'):
return row
return None
# Example
s = "banana$"
b = bwt(s)
print(f"BWT: {b}")
print(f"Inverse: {inverse_bwt(b)}")
""")
print("\n=== BWT with SA ===")
print("""
def bwt_from_sa(s, sa):
\"\"\"BWT from suffix array.\"\"\"
n = len(s)
bwt = [''] * n
for i, idx in enumerate(sa):
bwt[i] = s[idx - 1] if idx > 0 else s[-1]
return ''.join(bwt)
# Example
sa = build_suffix_array(s)
b = bwt_from_sa(s, sa)
print(f"BWT: {b}")
""")
print("\n=== Applications ===")
print("""
Applications:
- FM-index
- Genome alignment
- BWA, Bowtie
- Compression
- Bioinformatics
""")
print("\n=== Compression ===")
print("""
Compression:
- BWT clusters
- Better compression
- Run-length encoding
- Entropy coding
Example:
BWT "annbaa$"
โ Clusters
โ Compresses well
""")
print("\n=== Use Cases ===")
print("""
Use cases:
- Read alignment
- Genome indexing
- Compression
- Pattern matching
- Bioinformatics
""")๐ FM-index
๐ฏ FM-index
The FM-index is a compressed full-text index based on the Burrows-Wheeler Transform. It enables fast pattern matching with minimal memory, making it ideal for genome alignment.
FM-index
BWT: Basis. Compressed: Memory. Fast: O(m). Backward: Search. BWA: Uses. Bowtie: Uses.
# FM-index
print("=== What is FM-index? ===")
print("""
FM-index:
- Ferragina-Manzini index
- Based on BWT
- Compressed
- Fast search
- O(m) pattern search
Components:
- BWT
- C array (counts)
- Occ (occurrences)
""")
print("\n=== Components ===")
print("""
1. BWT:
- Burrows-Wheeler Transform
2. C:
- C[c] = number of chars < c
- First column counts
3. Occ:
- Occ(c, i) = occurrences of c in BWT[0..i]
- Rank
""")
print("\n=== Backward Search ===")
print("""
Backward search:
- Search pattern from right to left
- Update range [l, r]
- O(m)
Algorithm:
l = 0
r = n - 1
for i from m-1 down to 0:
c = P[i]
l = C[c] + Occ(c, l-1) + 1
r = C[c] + Occ(c, r)
if l > r: return not found
return [l, r]
""")
print("\n=== Example ===")
print("""
S = "banana$"
BWT = "annbaa$"
C:
$: 0
a: 1
b: 4
n: 5
Search "ana":
Start: l=0, r=6
i=2: c='a'
l = C['a'] + Occ('a', -1) + 1 = 1 + 0 + 1 = 2
r = C['a'] + Occ('a', 6) = 1 + 3 = 4
i=1: c='n'
l = C['n'] + Occ('n', 1) + 1 = 5 + 0 + 1 = 6
r = C['n'] + Occ('n', 4) = 5 + 2 = 7
r = min(r, 6) = 6
i=0: c='a'
l = C['a'] + Occ('a', 5) + 1 = 1 + 3 + 1 = 5
r = C['a'] + Occ('a', 6) = 1 + 3 = 4
l > r โ not found
Wait, need to fix indices.
""")
print("\n=== Python FM-index ===")
print("""
class FMIndex:
def __init__(self, s):
self.s = s
self.bwt = self._bwt(s)
self.c = self._c()
self.occ = self._occ()
def _bwt(self, s):
n = len(s)
rotations = [s[i:] + s[:i] for i in range(n)]
rotations.sort()
return ''.join(r[-1] for r in rotations)
def _c(self):
chars = sorted(set(self.bwt))
c = {}
total = 0
for ch in chars:
c[ch] = total
total += self.bwt.count(ch)
return c
def _occ(self):
occ = {}
for ch in set(self.bwt):
occ[ch] = [0]
count = 0
for b in self.bwt:
if b == ch:
count += 1
occ[ch].append(count)
return occ
def search(self, pattern):
l = 0
r = len(self.bwt) - 1
for ch in reversed(pattern):
if ch not in self.c:
return []
l = self.c[ch] + self.occ[ch][l]
r = self.c[ch] + self.occ[ch][r + 1] - 1
if l > r:
return []
return (l, r)
# Example
fm = FMIndex("banana$")
print(fm.search("ana"))
""")
print("\n=== BWA ===")
print("""
BWA:
- Burrows-Wheeler Aligner
- FM-index
- Short reads
- Fast, accurate
Modes:
- bwa aln (short)
- bwa mem (long)
- bwa bwasw (long)
Example:
bwa index genome.fa
bwa mem genome.fa reads.fq > aln.sam
""")
print("\n=== Bowtie ===")
print("""
Bowtie:
- FM-index
- Short reads
- Very fast
- Memory efficient
Example:
bowtie-build genome.fa genome
bowtie -S genome reads.fq > aln.sam
""")
print("\n=== Applications ===")
print("""
Applications:
- Read alignment
- Genome indexing
- Pattern matching
- Compression
- Bioinformatics
""")
print("\n=== Advantages ===")
print("""
Advantages:
- Compressed
- Fast
- Memory efficient
- Backward search
- Exact matching
""")
print("\n=== Disadvantages ===")
print("""
Disadvantages:
- Mismatches harder
- Indels harder
- Complex
- Implementation
""")
print("\n=== Use Cases ===")
print("""
Use cases:
- BWA
- Bowtie
- Genome alignment
- Variant calling
- RNA-seq
- ChIP-seq
""")๐งฌ Genome Suffix Structures
๐ฏ Indexing Genomes
Genome suffix structures are specialized data structures for indexing entire genomes. They must handle billions of characters while enabling fast queries.
Genome Suffix
Scale: Billions. Memory: Compressed. Speed: Fast. FM-index: Standard. BWT: Basis. Aligners: BWA/Bowtie.
# Genome Suffix Structures
print("=== Challenge ===")
print("""
Challenge:
- Human genome: 3 Gb
- 3 billion characters
- Index once
- Query millions
Memory:
- Suffix tree: ~50-100 GB
- Suffix array: ~12-24 GB
- FM-index: ~3-6 GB
- Compressed: ~1-2 GB
""")
print("\n=== Genome Indexing ===")
print("""
Indexing:
1. Read genome
2. Build BWT
3. Build FM-index
4. Store
Example:
genome.fa โ BWT โ FM-index
โ BWA index
""")
print("\n=== BWA Index ===")
print("""
BWA:
- FM-index
- Burrows-Wheeler
- Human genome: ~3 GB
Files:
- .amb
- .ann
- .bwt
- .pac
- .sa
Command:
bwa index genome.fa
""")
print("\n=== Bowtie Index ===")
print("""
Bowtie:
- FM-index
- Human genome: ~3 GB
Files:
- .1.ebwt
- .2.ebwt
- .3.ebwt
- .4.ebwt
- .rev.1.ebwt
- .rev.2.ebwt
Command:
bowtie-build genome.fa genome
""")
print("\n=== Memory Comparison ===")
print(f"{'Structure':<25} {'Human Genome':<20} {'Notes':<25}")
print("-" * 70)
print(f"{'Suffix tree':<25} {'~50-100 GB':<20} {'Too large':<25}")
print(f"{'Suffix array':<25} {'~12-24 GB':<20} {'Large':<25}")
print(f"{'FM-index':<25} {'~3-6 GB':<20} {'Standard':<25}")
print(f"{'Compressed FM':<25} {'~1-2 GB':<20} {'Best':<25}")
print(f"{'Bowtie index':<25} {'~3 GB':<20} {'Practical':<25}")
print(f"{'BWA index':<25} {'~3 GB':<20} {'Practical':<25}")
print("\n=== Genome Alignment ===")
print("""
Alignment:
- Reads (50-300 bp)
- Align to genome
- Millions of reads
Steps:
1. Index genome
2. Align reads
3. Output SAM/BAM
4. Analyze
Example:
bwa mem genome.fa reads.fq > aln.sam
samtools view -bS aln.sam > aln.bam
samtools sort aln.bam -o sorted.bam
""")
print("\n=== Pangenome ===")
print("""
Pangenome:
- Multiple genomes
- Graph structure
- FM-index on graph
Example:
vg toolkit
โ Graph FM-index
โ Alignment
""")
print("\n=== Graph Genomes ===")
print("""
Graph genomes:
- Variations as graph
- FM-index on graph
- Better alignment
Tools:
- vg
- GraphAligner
- Minigraph
""")
print("\n=== Compression ===")
print("""
Compression:
- BWT
- Run-length
- Entropy coding
- FM-index
Example:
Human genome
โ BWT
โ FM-index
โ 1-2 GB
""")
print("\n=== Use Cases ===")
print("""
Use cases:
- Read alignment
- Variant calling
- RNA-seq
- ChIP-seq
- Metagenomics
- Pangenomics
""")
print("\n=== Tools ===")
print("""
Tools:
- BWA
- Bowtie
- Bowtie2
- SOAP
- STAR
- HISAT2
- Minimap2
""")๐ฏ Read Alignment
๐ฏ Aligning Reads to Genomes
Read alignment is the process of finding the origin of short sequencing reads in a reference genome. Suffix trees and FM-indexes make this fast and memory-efficient.
Read Alignment
Reads: Short sequences. Reference: Genome. Index: FM-index. Mismatches: Allowed. Indels: Allowed. Output: SAM/BAM.
# Read Alignment
print("=== What is Read Alignment? ===")
print("""
Read alignment:
- Sequencing reads
- Reference genome
- Find origin
- Allow mismatches/indels
Example:
Read: ACGTACGT
Genome: ...ACGTACGT...
โ Align at position
""")
print("\n=== Steps ===")
print("""
Steps:
1. Index genome
2. Load reads
3. Align each read
4. Output SAM/BAM
5. Sort, index
6. Analyze
Example:
bwa index genome.fa
bwa mem genome.fa reads.fq > aln.sam
samtools sort aln.sam -o sorted.bam
samtools index sorted.bam
""")
print("\n=== FM-index Alignment ===")
print("""
FM-index alignment:
- Backward search
- Exact match
- Mismatches
- Indels
Approach:
- Find exact matches
- Extend with mismatches
- Allow indels
- Score
""")
print("\n=== BWA Algorithm ===")
print("""
BWA:
- FM-index
- Backward search
- Mismatches
- Indels
Steps:
1. Find exact matches
2. Extend
3. Score
4. Report best
""")
print("\n=== Bowtie Algorithm ===")
print("""
Bowtie:
- FM-index
- Backtracking
- Quality-aware
- Fast
Steps:
1. Align read
2. Allow mismatches
3. Report
""")
print("\n=== SAM Format ===")
print("""
SAM:
- Tab-delimited
- Alignment records
Fields:
QNAME: read name
FLAG: bitwise flag
RNAME: reference name
POS: position
MAPQ: mapping quality
CIGAR: alignment
RNEXT: mate reference
PNEXT: mate position
TLEN: insert size
SEQ: read sequence
QUAL: quality
Example:
read1 0 chr1 100 60 8M * 0 0 ACGTACGT IIIIIIII
""")
print("\n=== BAM Format ===")
print("""
BAM:
- Binary SAM
- Compressed
- Indexed
- Fast
Tools:
- samtools
- sambamba
Example:
samtools view -bS aln.sam > aln.bam
""")
print("\n=== Alignment Statistics ===")
print("""
Statistics:
- Total reads
- Aligned reads
- Uniquely aligned
- Multi-mapped
- Mapping quality
Example:
Total: 10M
Aligned: 9.5M (95%)
Unique: 9M (90%)
Multi: 0.5M (5%)
""")
print("\n=== Tools ===")
print("""
Tools:
- BWA
- Bowtie
- Bowtie2
- STAR
- HISAT2
- Minimap2
- SOAP
- GSNAP
""")
print("\n=== Applications ===")
print("""
Applications:
- Variant calling
- RNA-seq
- ChIP-seq
- ATAC-seq
- Metagenomics
- Cancer genomics
""")
print("\n=== Python Alignment ===")
print("""
import subprocess
def align_reads(genome, reads, output="aln.sam"):
\"\"\"Align reads with BWA.\"\"\"
# Index genome
subprocess.run(['bwa', 'index', genome], check=True)
# Align
with open(output, 'w') as f:
subprocess.run(
['bwa', 'mem', genome, reads],
stdout=f,
check=True
)
return output
# Use
# align_reads("genome.fa", "reads.fq")
""")๐ ๏ธ Tools (BWA, Bowtie, SAMtools)
๐ฏ Alignment Tools
Many tools exist for read alignment using suffix structures. Each has strengths and is suited to different use cases.
Tools
BWA: Standard. Bowtie: Fast. Bowtie2: Indels. STAR: RNA-seq. HISAT2: RNA-seq. Minimap2: Long reads.
# Tools
print("=== BWA ===")
print("""
BWA:
- Burrows-Wheeler Aligner
- FM-index
- Short reads
- Standard
Modes:
- bwa aln (short)
- bwa mem (long)
- bwa bwasw (long)
Example:
bwa index genome.fa
bwa mem genome.fa reads.fq > aln.sam
""")
print("\n=== Bowtie ===")
print("""
Bowtie:
- FM-index
- Short reads
- Very fast
- Memory efficient
Example:
bowtie-build genome.fa genome
bowtie -S genome reads.fq > aln.sam
""")
print("\n=== Bowtie2 ===")
print("""
Bowtie2:
- FM-index
- Indels
- Paired-end
- Gapped alignment
Example:
bowtie2-build genome.fa genome
bowtie2 -x genome -U reads.fq -S aln.sam
""")
print("\n=== STAR ===")
print("""
STAR:
- RNA-seq
- Spliced alignment
- Fast
- Suffix array
Example:
STAR --genomeDir genome --readFilesIn reads.fq --outFileNamePrefix out
""")
print("\n=== HISAT2 ===")
print("""
HISAT2:
- RNA-seq
- Spliced alignment
- Hierarchical
- Fast
Example:
hisat2-build genome.fa genome
hisat2 -x genome -U reads.fq -S aln.sam
""")
print("\n=== Minimap2 ===")
print("""
Minimap2:
- Long reads
- Assembly
- FM-index
- Fast
Example:
minimap2 -ax map-ont genome.fa reads.fq > aln.sam
""")
print("\n=== SAMtools ===")
print("""
SAMtools:
- SAM/BAM manipulation
- Sort, index, view
- Variant calling
Commands:
samtools view
samtools sort
samtools index
samtools mpileup
samtools faidx
""")
print("\n=== Other Tools ===")
print("""
1. SOAP
- Short reads
2. GSNAP
- Short reads
3. Novoalign
- Commercial
4. CLC
- Commercial
5. Bowtie2
- Indels
6. BWA-MEM2
- Faster BWA
""")
print("\n=== Tool Comparison ===")
print(f"{'Tool':<20} {'Type':<15} {'Speed':<15} {'Best For':<20}")
print("-" * 70)
print(f"{'BWA':<20} {'Short':<15} {'Fast':<15} {'General':<20}")
print(f"{'Bowtie':<20} {'Short':<15} {'Very fast':<15} {'Short reads':<20}")
print(f"{'Bowtie2':<20} {'Short':<15} {'Fast':<15} {'Indels':<20}")
print(f"{'STAR':<20} {'RNA-seq':<15} {'Fast':<15} {'Spliced':<20}")
print(f"{'HISAT2':<20} {'RNA-seq':<15} {'Fast':<15} {'Spliced':<20}")
print(f"{'Minimap2':<20} {'Long':<15} {'Fast':<15} {'Long reads':<20}")
print("\n=== When to Use Which ===")
print("""
USE BWA:
โ Short reads
โ DNA-seq
โ General
USE Bowtie:
โ Short reads
โ Fast
โ Memory
USE Bowtie2:
โ Indels
โ Paired-end
โ Gapped
USE STAR:
โ RNA-seq
โ Spliced
โ Fast
USE HISAT2:
โ RNA-seq
โ Spliced
โ Hierarchical
USE Minimap2:
โ Long reads
โ Assembly
โ ONT/PacBio
""")๐ Python Implementation
๐ฏ Suffix Structures in Python
Python is a powerful language for implementing and using suffix structures. Here's how.
Python
Implement: From scratch. Use: Libraries. Parse: SAM/BAM. Analyze: Data. Visualize: Plots. Automate: Pipeline.
# Python Implementation
print("=== 1. Naive Suffix Tree ===")
print("""
class SuffixTree:
def __init__(self, s):
self.s = s
self.root = {}
self._build()
def _build(self):
n = len(self.s)
for i in range(n):
self._insert(self.s[i:], i)
def _insert(self, suffix, index):
node = self.root
for ch in suffix:
if ch not in node:
node[ch] = {}
node = node[ch]
node['$'] = index
def search(self, pattern):
node = self.root
for ch in pattern:
if ch not in node:
return []
node = node[ch]
return self._collect(node)
def _collect(self, node):
results = []
if '$' in node:
results.append(node['$'])
for ch, child in node.items():
if ch != '$':
results.extend(self._collect(child))
return results
# Example
st = SuffixTree("banana$")
print(st.search("ana"))
""")
print("\n=== 2. Suffix Array ===")
print("""
def build_suffix_array(s):
\"\"\"Build suffix array naively.\"\"\"
return sorted(range(len(s)), key=lambda i: s[i:])
# Example
s = "banana$"
sa = build_suffix_array(s)
print(sa)
""")
print("\n=== 3. BWT ===")
print("""
def bwt(s):
\"\"\"Burrows-Wheeler Transform.\"\"\"
n = len(s)
rotations = [s[i:] + s[:i] for i in range(n)]
rotations.sort()
return ''.join(r[-1] for r in rotations)
def inverse_bwt(bwt):
\"\"\"Inverse BWT.\"\"\"
n = len(bwt)
table = [''] * n
for _ in range(n):
table = sorted(bwt[i] + table[i] for i in range(n))
for row in table:
if row.endswith('$'):
return row
return None
# Example
s = "banana$"
b = bwt(s)
print(f"BWT: {b}")
print(f"Inverse: {inverse_bwt(b)}")
""")
print("\n=== 4. FM-index ===")
print("""
class FMIndex:
def __init__(self, s):
self.s = s
self.bwt = bwt(s)
self.c = self._build_c()
self.occ = self._build_occ()
def _build_c(self):
chars = sorted(set(self.bwt))
c = {}
total = 0
for ch in chars:
c[ch] = total
total += self.bwt.count(ch)
return c
def _build_occ(self):
occ = {}
for ch in set(self.bwt):
occ[ch] = [0]
count = 0
for b in self.bwt:
if b == ch:
count += 1
occ[ch].append(count)
return occ
def search(self, pattern):
l = 0
r = len(self.bwt) - 1
for ch in reversed(pattern):
if ch not in self.c:
return []
l = self.c[ch] + self.occ[ch][l]
r = self.c[ch] + self.occ[ch][r + 1] - 1
if l > r:
return []
return (l, r)
# Example
fm = FMIndex("banana$")
print(fm.search("ana"))
""")
print("\n=== 5. Parse SAM ===")
print("""
def parse_sam(filepath):
\"\"\"Parse SAM file.\"\"\"
alignments = []
with open(filepath) as f:
for line in f:
if line.startswith('@'):
continue
fields = line.strip().split('\\t')
alignments.append({
'qname': fields[0],
'flag': int(fields[1]),
'rname': fields[2],
'pos': int(fields[3]),
'mapq': int(fields[4]),
'cigar': fields[5],
'seq': fields[9],
'qual': fields[10]
})
return alignments
# Use
# alignments = parse_sam("aln.sam")
""")
print("\n=== 6. Parse BAM ===")
print("""
import pysam
def parse_bam(filepath):
\"\"\"Parse BAM with pysam.\"\"\"
bam = pysam.AlignmentFile(filepath, "rb")
for read in bam:
print(read.query_name, read.reference_name, read.reference_start)
bam.close()
# Use
# parse_bam("sorted.bam")
""")
print("\n=== 7. Analysis ===")
print("""
import pandas as pd
def alignment_stats(alignments):
\"\"\"Compute alignment statistics.\"\"\"
df = pd.DataFrame(alignments)
stats = {
'total': len(df),
'aligned': len(df[df['rname'] != '*']),
'unique': len(df[(df['rname'] != '*') & (df['mapq'] > 0)]),
'multi': len(df[(df['rname'] != '*') & (df['mapq'] == 0)])
}
return stats
# Use
# stats = alignment_stats(alignments)
# print(stats)
""")
print("\n=== 8. Visualization ===")
print("""
import matplotlib.pyplot as plt
def plot_coverage(alignments, output="coverage.png"):
\"\"\"Plot coverage.\"\"\"
positions = [a['pos'] for a in alignments if a['rname'] != '*']
plt.figure(figsize=(12, 4))
plt.hist(positions, bins=100, color='purple')
plt.xlabel("Position")
plt.ylabel("Reads")
plt.title("Coverage")
plt.savefig(output)
plt.show()
# Use
# plot_coverage(alignments)
""")
print("\n=== 9. Complete Pipeline ===")
print("""
import subprocess
import pysam
def alignment_pipeline(genome, reads, output="aln.bam"):
\"\"\"Complete alignment pipeline.\"\"\"
# Index
subprocess.run(['bwa', 'index', genome], check=True)
# Align
sam = output.replace('.bam', '.sam')
with open(sam, 'w') as f:
subprocess.run(
['bwa', 'mem', genome, reads],
stdout=f,
check=True
)
# Sort
subprocess.run(
['samtools', 'sort', '-o', output, sam],
check=True
)
# Index
subprocess.run(['samtools', 'index', output], check=True)
return output
# Use
# bam = alignment_pipeline("genome.fa", "reads.fq")
""")
print("\n=== 10. Libraries ===")
print("""
Libraries:
- pydivsufsort (suffix array)
- pysam (SAM/BAM)
- Biopython (general)
- scikit-bio (general)
- suffix-tree (suffix tree)
- bwt (BWT)
""")โ๏ธ Comparison
๐ฏ Which Structure?
Choosing the right suffix structure depends on your needs: memory, speed, and functionality.
Comparison
Suffix tree: Flexible. Suffix array: Compact. BWT: Compressed. FM-index: Fast. Suffix automaton: DAG. Suffix trie: Simple.
| Structure | Build | Space | Search | Best For |
|---|---|---|---|---|
| Suffix Trie | O(nยฒ) | O(nยฒ) | O(m) | Teaching |
| Suffix Tree | O(n) | O(n) | O(m) | Flexibility |
| Suffix Array | O(n) | O(n) | O(m log n) | Compact |
| BWT | O(n) | O(n) | O(m) | Compression |
| FM-index | O(n) | O(n) | O(m) | Alignment |
| Suffix Automaton | O(n) | O(n) | O(m) | Substrings |
| Compressed SA | O(n) | O(n) | O(m log n) | Memory |
# Decision Guide
print("=== Choose Structure by Need ===")
print("""
Teaching:
โ Suffix trie
Flexibility:
โ Suffix tree
Compact:
โ Suffix array
Compression:
โ BWT
Alignment:
โ FM-index
Substrings:
โ Suffix automaton
Memory:
โ Compressed suffix array
""")
print("\n=== Space Comparison (Human Genome) ===")
print(f"{'Structure':<25} {'Memory':<20} {'Notes':<25}")
print("-" * 70)
print(f"{'Suffix trie':<25} {'~1000 GB':<20} {'Impractical':<25}")
print(f"{'Suffix tree':<25} {'~50-100 GB':<20} {'Too large':<25}")
print(f"{'Suffix array':<25} {'~12-24 GB':<20} {'Large':<25}")
print(f"{'BWT':<25} {'~3-6 GB':<20} {'Good':<25}")
print(f"{'FM-index':<25} {'~3-6 GB':<20} {'Standard':<25}")
print(f"{'Compressed FM':<25} {'~1-2 GB':<20} {'Best':<25}")
print("\n=== Speed Comparison ===")
print(f"{'Structure':<25} {'Build (1 GB)':<20} {'Search (100 bp)':<20}")
print("-" * 65)
print(f"{'Suffix tree':<25} {'~1 hour':<20} {'~1 ฮผs':<20}")
print(f"{'Suffix array':<25} {'~10 min':<20} {'~10 ฮผs':<20}")
print(f"{'FM-index':<25} {'~10 min':<20} {'~1 ฮผs':<20}")
print(f"{'BWA':<25} {'~1 hour':<20} {'~1 ms':<20}")
print("\n=== When to Use Which ===")
print("""
USE Suffix Tree:
โ Flexibility
โ Complex queries
โ Teaching
USE Suffix Array:
โ Compact
โ Binary search
โ General
USE BWT:
โ Compression
โ FM-index
USE FM-index:
โ Alignment
โ BWA/Bowtie
โ Fast search
USE Suffix Automaton:
โ Substrings
โ DAG
""")โ FAQ
๐ฏ Frequently Asked Questions
Answers to common questions about suffix trees and genome suffix structures.
What is a suffix tree?
A suffix tree is a compressed trie containing all the suffixes of a given string. It allows efficient pattern matching, substring queries, and many string operations in linear time. For a string of length n, a suffix tree can be built in O(n) time using Ukkonen's algorithm and uses O(n) space.
What is the difference between a suffix tree and a suffix array?
A suffix tree is a tree data structure with O(n) space but high constant factors. A suffix array is a sorted array of all suffixes of a string, using less memory and often faster in practice. Suffix arrays can be built in O(n) time and are used in FM-index and BWT-based aligners.
What is Ukkonen's algorithm?
Ukkonen's algorithm is a linear-time algorithm for constructing suffix trees. It builds the tree incrementally by adding characters one at a time, using suffix links and implicit suffixes to achieve O(n) time complexity. It is one of the most important algorithms in string processing.
What is the Burrows-Wheeler Transform (BWT)?
The Burrows-Wheeler Transform (BWT) is a reversible string transformation that rearranges characters to improve compression. It is the basis of the FM-index, which enables fast pattern matching and is used in genome aligners like BWA and Bowtie.
How are suffix trees used in genome analysis?
Suffix trees and related structures (suffix arrays, FM-index) are used for: (1) read alignment (BWA, Bowtie), (2) genome indexing, (3) repeat detection, (4) SNP discovery, (5) sequence assembly, (6) pattern matching, and (7) comparative genomics.
What is the FM-index?
The FM-index is a compressed full-text index based on the Burrows-Wheeler Transform. It enables fast pattern matching with minimal memory, making it ideal for genome alignment. BWA and Bowtie use FM-indexes.
What is a suffix array?
A suffix array is a sorted array of all suffixes of a string. It uses less memory than a suffix tree and is often faster in practice. Suffix arrays can be built in O(n) time using SA-IS or DC3 algorithms.
Why is the BWT useful?
The BWT is useful because it clusters similar characters together, improving compression. It's reversible, meaning the original string can be reconstructed. It's the basis of the FM-index, which is used in genome alignment.
What is the LCP array?
The LCP (Longest Common Prefix) array stores the length of the longest common prefix between adjacent suffixes in a suffix array. It's used for efficient pattern matching and other string algorithms.
Can I build a suffix tree in Python?
Yes. You can implement a naive suffix tree in Python, but for production use, consider libraries like pydivsufsort (suffix array), suffix-tree, or use external tools like BWA and Bowtie via subprocess.
# Quick Reference
print("=== Quick Answers ===")
print("""
Q: What is a suffix tree?
A: Compressed trie of all suffixes.
Q: Build time?
A: O(n) with Ukkonen.
Q: Space?
A: O(n).
Q: Search?
A: O(m) for pattern of length m.
Q: Suffix array?
A: Sorted suffixes, O(n) space.
Q: BWT?
A: Reversible transform, FM-index.
Q: FM-index?
A: Compressed index, BWA/Bowtie.
Q: Genome indexing?
A: FM-index for 3 Gb genome.
Q: Alignment tools?
A: BWA, Bowtie, STAR, Minimap2.
Q: Python?
A: pydivsufsort, pysam, suffix-tree.
Q: Free?
A: Yes โ all major tools are free.
""")๐ Future
๐ฏ Where Suffix Structures Are Going
The future is graph genomes, pangenomes, compressed indexes, AI, and quantum.
Future Directions
Graph: Pangenomes. Compressed: Better indexes. AI: Learned. Quantum: Quantum string. Cloud: Serverless. Single-cell: Resolution.
# Future of Suffix Structures
print("=== 1. Graph Genomes ===")
print("""
Trend: Graph genomes
Current:
- Linear reference
- Suffix tree/array
Future:
- Graph reference
- Pangenome
- FM-index on graph
Examples:
- vg toolkit
- GraphAligner
- Minigraph
Future:
- Graph FM-index
- Pangenome alignment
- Better variation
""")
print("\n=== 2. Compressed Indexes ===")
print("""
Trend: Compressed indexes
Current:
- FM-index
- 3-6 GB
Future:
- Better compression
- 1-2 GB
- Faster
Examples:
- RLBWT
- Run-length BWT
- Compressed SA
Future:
- Smaller
- Faster
- Better
""")
print("\n=== 3. AI Integration ===")
print("""
Trend: AI + suffix structures
Ideas:
- Learned indexes
- Neural search
- AI alignment
Examples:
- Deep learning
- Transformer
- Protein language models
Future:
- Learned indexes
- Faster
- Better
""")
print("\n=== 4. Quantum ===")
print("""
Trend: Quantum string algorithms
Ideas:
- Quantum suffix tree
- Quantum search
- Quantum alignment
Examples:
- Quantum algorithms
- Grover's search
- Quantum computing
Future:
- Quantum advantage
- Faster search
- New algorithms
""")
print("\n=== 5. Cloud-Native ===")
print("""
Trend: Cloud suffix structures
Current:
- Local indexes
- Self-managed
Future:
- Cloud-native
- Serverless
- Auto-scaling
Examples:
- AWS
- Google Cloud
- Azure
Future:
- Serverless
- Global
- Pay per use
""")
print("\n=== 6. Single-Cell ===")
print("""
Trend: Single-cell suffix structures
Current:
- Bulk genome
Future:
- Single-cell
- Cell-type specific
- Index
Examples:
- scRNA-seq
- Single-cell DNA
- Cell atlas
Future:
- Single-cell indexes
- Cell-type resolution
- Dynamics
""")
# TIMELINE
print("\n=== Timeline ===")
print("""
Past:
1973: Weiner suffix tree
1976: McCreight
1995: Ukkonen
1990s: Suffix arrays
1994: BWT
2000: FM-index
2009: BWA, Bowtie
Present:
2025: Graph genomes
2026: Compressed indexes
Future:
2030: AI indexes
2035: Quantum
2040: ?
""")
print("\n=== Final Thoughts ===")
print("""
Suffix structures are:
- Fundamental to string algorithms
- Essential for bioinformatics
- Used in genome alignment
- Evolving
The future is:
- Graph genomes
- Compressed indexes
- AI integration
- Quantum
- Cloud-native
- Single-cell
"Index once, query forever."
Keep exploring. Keep building.
""")