Module 3 — Tổng kết & cheat sheet
Recap graph algorithms: bảng chọn thuật toán shortest-path, cheat sheet BFS/DFS/MST/DSU, glossary, 5 pitfall và self-assessment 4 câu.
TL;DR: Module 3 đi qua toàn bộ graph algorithms cốt lõi — từ representation đến BFS/DFS, topo sort, ba thuật toán shortest-path (Dijkstra, Bellman-Ford, Floyd-Warshall), MST (Kruskal + Prim), và Disjoint Set Union. Trang này là cheat sheet để bookmark: bảng chọn thuật toán theo bài toán, bảng so sánh shortest-path theo điều kiện trọng số và scale, glossary 15 thuật ngữ, 5 pitfall và self-assessment. Nếu bạn giải thích được khi nào dùng Dijkstra thay Bellman-Ford, hiểu tại sao DSU gần O(1), và biết design giải pháp graph cho bài toán thật — bạn đã sẵn sàng cho khoá tiếp theo.
Đã đi qua những gì
Bạn bắt đầu từ bài 00 — Tổng quan module: graph không phải cấu trúc xa lạ — Maven, Git, Google Maps đều chạy graph algorithm mỗi ngày.
Bài 01 — Graph representation đặt nền: adjacency list tốn O(V+E) space phù hợp sparse graph, adjacency matrix tốn O(V²) nhưng lookup O(1) phù hợp dense graph nhỏ. Choice sai ở đây có thể gây OOM trước khi algorithm chạy.
Bài 02 — BFS duyệt theo tầng: tìm shortest path không trọng số trong O(V+E), phát hiện connected component, là backbone của bidirectional search. Queue FIFO là điểm mấu chốt — thay queue bằng stack là thành DFS.
Bài 03 — DFS duyệt sâu trước: phát hiện cycle qua back edge, topological sort qua finish-time order, và strongly connected component (Kosaraju/Tarjan). Recursion stack hoặc explicit stack — hai cách implement tương đương.
Bài 04 — Topological sort là ứng dụng DFS/Kahn trên DAG: thứ tự build Maven, dependency resolution, detect circular import. Kahn BFS-based dễ detect cycle hơn (check order.size() != V).
Bài 05 — Dijkstra giải shortest path với trọng số không âm: relaxation + MinHeap cho O((V+E) log V). Greedy hoạt động vì một khi đỉnh settled, dist của nó đã tối ưu — điều này break khi có trọng số âm.
Bài 06 — Bellman-Ford tổng quát hơn: chạy được trọng số âm, detect negative cycle. Đánh đổi: O(VE) thay vì O((V+E) log V) — chậm hơn nhưng đúng hơn trong điều kiện khó.
Bài 07 — Floyd-Warshall giải all-pairs shortest path bằng DP 3 vòng lặp O(V³): đơn giản code, phù hợp V nhỏ (dưới vài trăm đỉnh), cho phép detect negative cycle qua diagonal âm.
Bài 08 — MST (Kruskal + Prim) tìm cây khung chi phí tối thiểu: Kruskal sort cạnh + DSU cho sparse graph, Prim MinHeap-based cho dense graph. Ứng dụng: cáp mạng datacenter, đường truyền tối ưu.
Bài 09 — Disjoint Set Union cấu trúc gom nhóm gần O(1): path compression rút phẳng cây về root, union by rank giữ cây thấp. Kết hợp hai kỹ thuật cho độ phức tạp O(α(n)) — hàm Ackermann nghịch đảo, tăng cực kỳ chậm.
Bài 10 — Mini Challenge: Maze BFS thực hành BFS trên grid 2D: mỗi ô là đỉnh, 4 hướng là cạnh, tìm đường ngắn nhất từ S đến E.
Bài 11 — Case Study: Maven + Google Maps dive sâu hai hệ thống production: Maven dùng Kahn topo sort để quyết định build order (cycle → CycleDetectedException), Google Maps dùng Dijkstra + Contraction Hierarchy + bidirectional search để routing dưới 10ms trên graph 100 triệu cạnh — nhanh hơn Dijkstra naive khoảng 40.000 lần.
🗺️ Cheat sheet
BFS vs DFS — khi nào dùng cái nào
| Tiêu chí | BFS | DFS |
|---|---|---|
| Cấu trúc | Queue FIFO | Stack / đệ quy |
| Duyệt theo | Tầng (level-order) | Nhánh sâu trước |
| Shortest path | Có (không trọng số) | Không |
| Cycle detection | Có | Có (back edge rõ hơn) |
| Topo sort | Có (Kahn) | Có (finish-time) |
| Connected component | Có | Có |
| Ứng dụng tốt | Shortest hop, BFS grid, bidirectional search | Cycle detect, topo sort, SCC, backtracking |
| Độ phức tạp | O(V+E) | O(V+E) |
Bảng chọn thuật toán shortest-path
| Điều kiện | Thuật toán | Độ phức tạp | Ghi chú |
|---|---|---|---|
| Không trọng số (hop count) | BFS | O(V+E) | Nhanh nhất cho unweighted |
| Trọng số dương, single-source | Dijkstra | O((V+E) log V) | Default choice khi không có trọng số âm |
| Trọng số âm, single-source | Bellman-Ford | O(VE) | Detect negative cycle, chậm hơn Dijkstra |
| Dense graph nhỏ, single-source | Dijkstra + matrix | O(V²) | Matrix Dijkstra không dùng heap, tốt khi E ~ V² |
| All-pairs, V nhỏ (dưới vài trăm) | Floyd-Warshall | O(V³) | Code đơn giản, detect negative cycle qua diagonal |
| All-pairs, V lớn không âm | Dijkstra × V lần | O(V(V+E) log V) | Chạy Dijkstra từ mỗi đỉnh |
| Graph có negative cycle | Bellman-Ford | O(VE) | Dijkstra sai, Floyd-Warshall detect được |
MST — Kruskal vs Prim
| Tiêu chí | Kruskal | Prim |
|---|---|---|
| Approach | Sort cạnh, thêm cạnh nhẹ nhất không tạo cycle | Grow cây từ đỉnh nguồn, thêm đỉnh gần nhất |
| Cấu trúc phụ | DSU (Union-Find) | MinHeap |
| Phù hợp | Sparse graph (E nhỏ) | Dense graph (E lớn) |
| Độ phức tạp | O(E log E) | O((V+E) log V) |
| Detect cycle | DSU find() so sánh root | Không cần (chỉ thêm đỉnh chưa visited) |
DSU — path compression + union by rank
function find(x):
if parent[x] != x:
parent[x] <- find(parent[x]) -- path compression: rút phẳng về root
return parent[x]
function union(x, y):
rx <- find(x)
ry <- find(y)
if rx = ry: return false -- cùng nhóm, không union
if rank[rx] < rank[ry]: -- union by rank: gắn cây thấp vào cây cao
swap(rx, ry)
parent[ry] <- rx
if rank[rx] = rank[ry]: rank[rx] <- rank[rx] + 1
return true
// Time: O(α(n)) mỗi thao tác Space: O(n)
// α(n) = hàm Ackermann nghịch đảo, thực tế ≤ 4 với mọi n hợp lý
Algorithm vs production use case
| Use case | Algorithm | Bài tham khảo |
|---|---|---|
| Maven build order | Topo sort (Kahn) | Bài 04, 11 |
| Detect circular import | DFS cycle detection | Bài 03 |
| Shortest hop (social graph) | BFS | Bài 02 |
| GPS routing | Dijkstra + CH + A* | Bài 05, 11 |
| Currency arbitrage detect | Bellman-Ford negative cycle | Bài 06 |
| Datacenter cabling tối ưu | Kruskal / Prim MST | Bài 08 |
| Image connected component | DSU Union-Find | Bài 09 |
| All-pairs latency (nhỏ) | Floyd-Warshall | Bài 07 |
| Maze / grid pathfinding | BFS trên grid | Bài 10 |
📖 Glossary module
| Thuật ngữ | Định nghĩa 1 câu |
|---|---|
| Đỉnh (vertex/node) | Thực thể trong đồ thị — thành phố, module, người dùng. |
| Cạnh (edge) | Quan hệ giữa 2 đỉnh — đường đi, dependency, kết bạn. |
| Adjacency list | Lưu graph bằng danh sách hàng xóm cho từng đỉnh — O(V+E) space, phù hợp sparse graph. |
| Adjacency matrix | Lưu graph bằng bảng V×V — O(V²) space, lookup O(1), phù hợp dense graph nhỏ. |
| BFS | Breadth-First Search — duyệt theo tầng dùng queue, tìm shortest hop. |
| DFS | Depth-First Search — duyệt sâu trước dùng stack/đệ quy, tìm cycle và topo sort. |
| DAG | Directed Acyclic Graph — đồ thị có hướng không có chu trình, nền tảng của topo sort và DP. |
| Topological sort | Sắp xếp đỉnh của DAG sao cho mọi cạnh đi từ trái sang phải — build order, task scheduling. |
| Relaxation | Cập nhật dist[v] nếu tìm được đường ngắn hơn qua u — thao tác cốt lõi của Dijkstra và Bellman-Ford. |
| Dijkstra | Shortest path trọng số dương dùng MinHeap + greedy relaxation — O((V+E) log V). |
| Bellman-Ford | Shortest path chạy được trọng số âm, detect negative cycle — O(VE). |
| Floyd-Warshall | All-pairs shortest path bằng DP O(V³) — đơn giản, phù hợp V nhỏ. |
| MST | Minimum Spanning Tree — cây khung bao gồm mọi đỉnh với tổng trọng số cạnh nhỏ nhất. |
| DSU / Union-Find | Disjoint Set Union — cấu trúc gom nhóm với path compression + union by rank, O(α(n)) mỗi thao tác. |
| α(n) | Hàm Ackermann nghịch đảo — tăng cực kỳ chậm, thực tế ≤ 4 với mọi n hợp lý trong máy tính. |
⚠️ Pitfall tổng hợp
Pitfall 1 — Dùng Dijkstra khi có trọng số âm. Dijkstra greedy assume rằng một khi đỉnh settled, dist của nó đã tối ưu. Với trọng số âm, assumption này sai — một đường đi dài hơn có thể rẻ hơn nhờ cạnh âm sau đó. Kết quả: dist sai hoàn toàn mà không có thông báo lỗi. Fix: dùng Bellman-Ford hoặc Johnson's algorithm.
Pitfall 2 — Quên khởi tạo visited dẫn đến BFS/DFS vô hạn trên đồ thị có chu trình.
BFS/DFS trên undirected graph nếu không đánh dấu visited trước khi thêm vào queue/stack sẽ thêm cùng một đỉnh nhiều lần — queue phình ra, memory hết, hoặc vòng lặp vô hạn. Fix: mark visited ngay khi enqueue (BFS) hoặc ngay khi gọi DFS, không phải khi xử lý.
Pitfall 3 — Nhầm Kruskal O(E log E) với O(E log V) — thực ra giống nhau nhưng sort cạnh mới là bottleneck. Nhiều người nghĩ DSU là bottleneck của Kruskal. Thực ra DSU O(α(n)) gần như O(1) — bottleneck là sort E cạnh, O(E log E). Vì E ≤ V², nên log E ≤ 2 log V, do đó O(E log E) = O(E log V). Với sparse graph (E ~ V), Kruskal nhanh hơn Prim.
Pitfall 4 — Floyd-Warshall khởi tạo sai dist[i][i].
dist[i][i] phải là 0 (đứng yên không tốn chi phí), không phải vô cực. Nếu khởi tạo sai, mọi path đi qua i sẽ tính sai. Ngoài ra, detect negative cycle bằng cách check dist[i][i] < 0 sau khi chạy xong — nếu âm, có negative cycle đi qua i.
Pitfall 5 — DSU forget path compression làm thao tác chậm từ O(α(n)) xuống O(log n) hoặc O(n). DSU không có path compression vẫn đúng kết quả nhưng chậm hơn đáng kể — cây có thể bị unbalanced, find() phải đi lên từng bước. Với Kruskal trên graph triệu cạnh, difference giữa O(α(n)) và O(log n) rất rõ. Luôn implement cả path compression lẫn union by rank.
✅ Self-assessment
Bạn đã đạt module này nếu trả lời được:
- Implement được BFS và DFS, áp dụng cho shortest-path không trọng số, cycle detection và topological sort — viết pseudocode từ đầu không cần nhìn tài liệu.
- Nếu chưa: đọc lại bài 02 (BFS, mục queue walkthrough), bài 03 (DFS, mục back edge), bài 04 (topo sort).
- Compare được Dijkstra, Bellman-Ford, Floyd-Warshall — giải thích khi nào dùng cái nào dựa trên trọng số âm, scale (V/E), và single-source vs all-pairs.
- Nếu chưa: đọc lại bài 05 (Dijkstra, mục tại sao greedy đúng), bài 06 (Bellman-Ford, mục negative weight), bài 07 (Floyd-Warshall, mục so sánh).
- Explain được Union-Find với path compression và union by rank — tại sao kết hợp hai kỹ thuật cho O(α(n)), và α(n) có nghĩa gì thực tế.
- Nếu chưa: đọc lại bài 09 (DSU, mục cơ chế bên dưới và amortized analysis).
- Design được giải pháp đồ thị cho bài toán thật — cho một hệ thống (routing, dependency, MST), chọn đúng algorithm và lý giải được tradeoff.
- Nếu chưa: đọc lại bài 11 (case study Maven + Google Maps) và bảng "Algorithm vs production use case" ở trên.
🚀 What's next
Module 3 là bài cuối của khoá Thuật toán Cốt lõi (tier 2 trong lộ trình thuật toán). Bạn đã hoàn thành nền tảng: data structure nâng cao, tìm kiếm nhanh, sắp xếp, và graph algorithms.
Khoá tiếp theo là Thuật toán Ứng dụng (tier 3) — đào sâu các kỹ thuật giải bài toán phức tạp hơn: Dynamic Programming (overlapping subproblems, optimal substructure), String algorithms (KMP, Rabin-Karp, Suffix Array), và Big Data algorithms (Bloom Filter, Count-Min Sketch, HyperLogLog). Khoá này đang trong quá trình phát triển.
📚 Tài liệu mở rộng
- Sách: Introduction to Algorithms (CLRS) — Cormen, Leiserson, Rivest, Stein. Chapters 22-25 bao trọn mọi graph algorithm trong module này với proof đầy đủ. Chapter 22 (BFS/DFS) và 24 (Dijkstra/Bellman-Ford) là tham khảo chuẩn.
- Sách: Algorithm Design — Jon Kleinberg, Éva Tardos. Chapter 3 (Graph traversal) và 4 (Greedy — Dijkstra, MST) trình bày theo hướng "tại sao đúng" thay vì chỉ "cách làm".
- Visualizer: VisuAlgo — Graph — animation BFS, DFS, Dijkstra, Bellman-Ford, Floyd-Warshall, MST tương tác. Dùng khi cần xem từng bước algorithm chạy.
- Paper: "Contraction Hierarchies: Faster and Simpler Hierarchical Routing in Road Networks" — Geisberger et al. (2008). Kỹ thuật core của Google Maps routing — đọc section 3 (node contraction) sau khi hiểu Dijkstra vững.
- Paper: "Shortest Paths in Digraphs of Small Treewidth" — Chaudhuri & Zaroliagis (ICALP 1995, Algorithmica) — đào sâu hơn về routing optimization nếu muốn hiểu tại sao CH hiệu quả trên road network.
Bài tiếp theo: Thuật toán Ứng dụng — DP, String, Big Data
Bài này có giúp bạn hiểu bản chất không?
Hỏi đáp về bài này
Chưa có câu hỏi
Có gì chưa rõ trong bài? Đặt câu hỏi đầu tiên — câu trả lời từ cộng đồng giúp bạn (và người sau).
Đặt câu hỏi đầu tiên