Suffix Tree - Cây Hậu Tố¶
Tác giả: FPTOJ Team
Nội dung tham khảo từ: CP-Algorithms - Suffix Tree
Lưu ý
Bài viết này giải thích khái niệm Suffix Tree và so sánh với Suffix Array. Code minh họa bên dưới xây dựng Suffix Array (với LCP bằng Kasai's algorithm), không phải Suffix Tree đầy đủ. Giải thuật Ukkonen để xây dựng Suffix Tree thực sự rất phức tạp và nằm ngoài phạm vi bài viết.
1. Bản chất vấn đề¶
Định nghĩa¶
Suffix Tree của xâu \(S\) là cây có hướng trong đó mỗi cạnh được gán nhãn bằng 1 xâu con của \(S\), và mỗi hậu tố của \(S\) tương ứng với 1 đường đi từ gốc đến lá (hoặc nút trong).
So sánh¶
| Cấu trúc | Xây dựng | Không gian | Ứng dụng |
|---|---|---|---|
| Suffix Array | \(O(N \log N)\) | \(O(N)\) | Nhiều bài toán xâu |
| Suffix Tree | \(O(N)\) | \(O(N)\) | Ukkonen's algorithm |
| Trie | $O(N \cdot | \Sigma | )$ |
Ứng dụng¶
| Bài toán | Thời gian với Suffix Tree | Thời gian với Suffix Array |
|---|---|---|
| Tìm xâu con | \(O(M)\) (\(M\) = độ dài pattern) | \(O(M \log N)\) (tìm kiếm nhị phân) |
| Xâu con chung dài nhất (LCS) | \(O(N)\) | \(O(N)\) (với LCP + RMQ) |
| Đếm xâu con phân biệt | \(O(N)\) | \(O(N)\) (công thức: \(\frac{N(N+1)}{2} - \sum LCP[i]\)) |
| Xâu lặp lại dài nhất | \(O(N)\) | \(O(N)\) (max LCP) |
Ví dụ cụ thể với xâu "banana"¶
Các hậu tố của "banana$" (\(\$\) là ký tự kết thúc):
| Chỉ số | Hậu tố | Thứ tự từ điển (SA) |
|---|---|---|
| 0 | banana$ | 6: $ |
| 1 | anana$ | 5: a$ |
| 2 | nana$ | 3: ana$ |
| 3 | ana$ | 1: anana$ |
| 4 | na$ | 0: banana$ |
| 5 | a$ | 4: na$ |
| 6 | $ | 2: nana$ |
Suffix Tree cho "banana" gộp các cạnh có nhánh đơn thành một cạnh duy nhất (ví dụ: "ana" thay vì "a" → "n" → "a"). Điều này giúp giảm bộ nhớ từ \(O(N^2)\) xuống \(O(N)\).
2. Tư duy cốt lõi¶
Ukkonen's Algorithm¶
Xây Suffix Tree online, từng ký tự một. Mỗi bước thêm \(S[i]\) vào cây.
Kỹ thuật:
- Suffix Link: Giống Palindrome Tree, giúp nhảy nhanh.
- Rule 2 (Showstopper): Khi cần tạo nút mới.
- Implicit Suffix Tree: Chỉ lưu các suffix đang hoạt động.
Cấu trúc Suffix Tree cho xâu "banana$"¶
graph TD
R["Root"] --> A1["a: [1,3]"]
R --> B["b: [0,0]"]
R --> N1["n: [2,3]"]
R --> D["$ (lá)"]
A1 --> N2["na: [4,6]"]
A1 --> D2["$ (lá)"]
B --> A2["anana$: (lá)"]
N2 --> D3["$ (lá)"]
N2 --> N3["na: [5,6]"]
N3 --> D4["$ (lá)"]
N1 --> A3["ana$: (lá)"]
N1 --> D5["$ (lá)"]
3. Đánh giá độ phức tạp¶
| Thao tác | Thời gian | Không gian |
|---|---|---|
| Xây Suffix Tree (Ukkonen) | \(O(N)\) | \(O(N)\) |
| Tìm pattern | \(O(M)\) | \(O(1)\) |
Code minh họa¶
Suffix Tree đơn giản (không tối ưu — mục đích học tập)¶
Bài tập luyện tập¶
| Mã bài | Tên bài tập | Độ khó | Chủ đề |
|---|---|---|---|
str-subcnt |
Đếm xâu con phân biệt (Suffix Tree) | ⭐⭐⭐ | Ứng dụng cơ bản của Suffix Tree |
str-match |
Đếm xuất hiện mẫu (Suffix Tree) | ⭐⭐⭐ | Tìm pattern trong văn bản |
str-firstc |
Xâu con phân biệt bắt đầu bằng ký tự | ⭐⭐ | Đếm theo ký tự đầu |
str-fc |
Ký tự đầu tiên xuất hiện duy nhất | ⭐⭐ | Bài tập cơ bản |
str-xor |
LCP xoay vòng (Suffix Tree) | ⭐⭐⭐ | LCP giữa hai hậu tố |
stra-dist |
Đếm xâu con phân biệt (Suffix Array) | ⭐⭐⭐⭐ | Công thức kinh điển |
stra-lcp |
LCP và truy vấn trên Suffix Array | ⭐⭐⭐⭐ | SA + LCP + RMQ |
stra-lcs |
Xâu con chung dài nhất (Suffix Array) | ⭐⭐⭐⭐ | Kết hợp hai xâu |
saf-salc |
In Suffix Array và LCP | ⭐⭐ | Bài tập cơ bản |