Minimum Spanning Tree — Kruskal vs Prim, cut property
MST: kết nối nhiều node chi phí tối thiểu không vòng lặp. Kruskal (sort edges + DSU) và Prim (MinHeap), cut property chứng minh correctness, khi nào chọn từng thuật toán.
TL;DR: Minimum Spanning Tree (MST) là spanning tree (kết nối mọi vertex, V-1 cạnh, không cycle) với tổng edge weight nhỏ nhất. Hai thuật toán kinh điển: Kruskal — sort toàn bộ cạnh tăng dần, thêm từng cạnh nếu không tạo cycle (dùng DSU để kiểm tra), O(E log V); Prim — grow tree từ một vertex, MinHeap chứa các cạnh ra ngoài tree, luôn pick cạnh nhỏ nhất, O((V+E) log V). Cả hai đều dựa trên cùng một nền tảng lý thuyết: cut property — edge nhỏ nhất qua bất kỳ cut (S, V-S) nào phải thuộc ít nhất một MST.
Một network engineer cần kết nối 100 datacenter bằng cáp quang. Mỗi cặp datacenter có chi phí lắp đặt khác nhau — distance, terrain, licensing. Mục tiêu: tìm tập cáp với tổng chi phí nhỏ nhất sao cho mọi datacenter đều được kết nối với nhau.
Thêm cáp thừa (tạo vòng lặp) là lãng phí — nếu datacenter A và B đã kết nối qua C, thêm cáp trực tiếp A–B chỉ tốn thêm tiền mà không tăng connectivity. Cấu hình tối ưu là một tree — connected, acyclic, đúng V-1 cạnh cho V đỉnh.
Bài này dạy 2 algorithm tìm Minimum Spanning Tree: Kruskal (sort edges và dùng Disjoint Set Union — preview lesson 09), Prim (grow tree với MinHeap), khi nào chọn cái nào, và cut property làm nền tảng correctness cho cả hai.
1. Analogy — Thiết kế đường dây điện
Có N ngôi nhà cần điện. Kỹ sư đo chi phí rải dây giữa từng cặp nhà — khoảng cách, địa hình, vật cản. Cần tìm cách rải dây với tổng chi phí nhỏ nhất sao cho mọi nhà đều có điện.
Nếu rải dây tạo vòng lặp — nhà A kết nối với B qua cả đường thẳng lẫn đường vòng qua C — thì đường vòng hoàn toàn thừa. Cấu hình tối ưu: N nhà, N-1 đường dây, không vòng lặp, mọi nhà kết nối. Đây chính xác là spanning tree.
| Thiết kế đường điện | MST |
|---|---|
| Ngôi nhà | Vertex |
| Đường dây giữa hai nhà | Edge |
| Chi phí rải dây | Edge weight |
| Mọi nhà có điện | Connected graph |
| Không vòng lặp thừa | Acyclic (tree) |
| Tổng chi phí nhỏ nhất | Minimum total weight |
MST = spanning tree (kết nối mọi vertex, V-1 edge, không cycle) với tổng weight nhỏ nhất. Không tối ưu từng đường đi riêng lẻ — tối ưu tổng chi phí toàn bộ cây.
2. Định nghĩa MST
Cho graph G = (V, E) connected, undirected, weighted:
- Spanning tree: tập con E' của E sao cho tất cả V vertex kết nối với nhau, đúng V-1 edges, không cycle.
- MST (Minimum Spanning Tree): spanning tree với tổng weight nhỏ nhất.
Số lượng MST:
- Nếu mọi edge weight khác nhau → MST là duy nhất.
- Nếu có tie (edge weight bằng nhau) → có thể tồn tại nhiều MST có cùng tổng weight tối thiểu.
Lưu ý quan trọng: MST tối ưu tổng weight toàn cây, không tối ưu từng shortest path riêng lẻ giữa các cặp vertex. Đây là điểm khác biệt cốt lõi so với Dijkstra.
3. Cut property — nền tảng correctness
Cut là cách chia tập vertex V thành 2 disjoint set: S và V-S (mọi vertex thuộc đúng một trong hai set).
Cut edge là edge có một endpoint trong S và một endpoint trong V-S — nghĩa là edge này "bắc cầu" qua cut.
Cut property: với bất kỳ cut (S, V-S) nào, edge có weight nhỏ nhất qua cut đó phải thuộc ít nhất một MST.
Tại sao? Gọi edge đó là e. Giả sử e không thuộc MST T. Thêm e vào T tạo ra đúng một cycle — cycle đó phải chứa ít nhất một edge f khác qua cut này (vì cycle phải thoát ra và quay vào S). Do e là edge nhỏ nhất qua cut: weight(e) <= weight(f). Thay f bằng e trong T cho spanning tree T' với tổng weight không tăng — mâu thuẫn với T là MST (hoặc T' cũng là MST nếu tie).
Ý nghĩa thực tế: cả Kruskal và Prim đều hoạt động bằng cách lặp lại cut property — tại mỗi bước, pick edge nhỏ nhất qua một cut nào đó, đảm bảo edge đó thuộc MST.
4. Kruskal algorithm
4.1 Ý tưởng
Kruskal xây MST theo góc nhìn edge-centric:
- Sort tất cả edge theo weight ascending.
- Iterate qua từng edge (từ nhỏ đến lớn): thêm edge vào MST nếu hai endpoint chưa nằm trong cùng một component (tức là thêm edge này không tạo cycle).
- Dừng khi đã có V-1 edges.
Bước 2 — "hai endpoint có trong cùng component không?" — được giải bằng Disjoint Set Union (DSU). DSU sẽ được phân tích chi tiết ở lesson 09; ở đây dùng như black box với hai operation: find(x) trả root của component chứa x, union(x, y) gộp hai component.
4.2 Pseudocode
Kruskal(G, V, E):
-- Sắp xếp tất cả cạnh theo trọng số tăng dần
sắp xếp E theo weight tăng dần
DSU dsu <- khởi tạo với V đỉnh -- mỗi đỉnh là component riêng
result <- []
for each cạnh (u, v, w) trong E (theo thứ tự đã sắp xếp):
-- Chỉ thêm nếu u và v thuộc component khác nhau (không tạo cycle)
if dsu.union(u, v) = true:
result.append((u, v, w))
if result.length = V - 1:
break -- MST đã đủ V-1 cạnh
return result -- nếu result.length < V-1: graph không liên thông
Time: O(E log E) để sort + O(E × α(V)) cho DSU ≈ O(E log V) Space: O(V)
α là inverse Ackermann — tăng cực kỳ chậm, thực tế coi như hằng số.
4.3 Trace ví dụ — graph 4 vertex
0 ---(1)--- 1
| \ |
(4) (3) (2)
| \ |
3 ---(5)--- 2
Edges sorted: (0,1,1), (1,2,2), (0,2,3), (0,3,4), (2,3,5).
| Bước | Edge | DSU state | Kết quả |
|---|---|---|---|
| 1 | (0,1) w1 | find(0)=0, find(1)=1 → khác → union | Thêm. MST: 1 |
| 2 | (1,2) w2 | find(1)=0, find(2)=2 → khác → union | Thêm. MST: 2 |
| 3 | (0,2) w3 | find(0)=0, find(2)=0 → cùng component | Bỏ qua (tạo cycle) |
| 4 | (0,3) w4 | find(0)=0, find(3)=3 → khác → union | Thêm. MST: 3 |
V-1 = 3 cạnh đã có. Dừng. Tổng weight = 1+2+4 = 7.
5. Prim algorithm
5.1 Ý tưởng
Prim xây MST theo góc nhìn vertex-centric:
- Bắt đầu từ một vertex bất kỳ (vertex 0).
- Maintain MinHeap chứa các edge từ "tree-set" (các vertex đã thuộc MST) ra "non-tree" (các vertex chưa thuộc MST).
- Lặp: lấy edge nhỏ nhất từ heap; nếu endpoint đích chưa trong tree — thêm vertex đó vào tree, thêm edge vào MST, đẩy tất cả edge từ vertex mới ra các neighbor chưa trong tree vào heap.
- Dừng khi tất cả vertex trong tree.
Đây là cut property applied trực tiếp: tại mỗi bước, cut là (tree-set, V - tree-set), và Prim luôn pick edge nhỏ nhất qua cut đó.
5.2 Pseudocode
Prim(G, n):
inTree[0..n-1] <- false
H <- MinHeap rỗng -- keyed by edge weight
result <- []
-- Bắt đầu từ đỉnh 0
inTree[0] <- true
for each cạnh (0, v, w) kề đỉnh 0:
H.push((0, v, w))
while H không rỗng và result.length < n - 1:
(u, v, w) <- H.pop() -- lấy cạnh có trọng số nhỏ nhất
if inTree[v]:
continue -- v đã trong tree, bỏ qua (lazy delete)
-- Thêm v vào tree
inTree[v] <- true
result.append((u, v, w))
-- Đẩy các cạnh từ v ra neighbor chưa trong tree
for each cạnh (v, x, wx) kề v:
if not inTree[x]:
H.push((v, x, wx))
return result
Time: O((V+E) log V) Space: O(V+E)
5.3 Trace ví dụ — cùng graph 4 vertex
| Bước | Poll | inTree | Kết quả |
|---|---|---|---|
| Init | — | 0 | Heap: [(0→1,1),(0→2,3),(0→3,4)] |
| 1 | (0→1, w1) | 1 | Thêm. Push 1→2 w2. Heap: [(1→2,2),(0→2,3),(0→3,4)] |
| 2 | (1→2, w2) | 2 | Thêm. Push 2→3 w5. Heap: [(0→2,3),(0→3,4),(2→3,5)] |
| 3 | (0→2, w3) | — | vertex 2 đã trong tree → skip |
| 4 | (0→3, w4) | 3 | Thêm. Heap: [(2→3,5)] |
MST: 4, tổng weight = 7. Kết quả giống Kruskal.
graph LR
subgraph "Kruskal — thêm cạnh nhỏ nhất không tạo cycle"
K0((0)) -- "w=1" --- K1((1))
K1 -- "w=2" --- K2((2))
K0 -- "w=4" --- K3((3))
end
subgraph "Prim — grow tree từ đỉnh 0"
P0((0)) -- "w=1" --- P1((1))
P1 -- "w=2" --- P2((2))
P0 -- "w=4" --- P3((3))
end6. Kruskal vs Prim — khi nào chọn gì
| Aspect | Kruskal | Prim |
|---|---|---|
| Approach | Edge-sorted + DSU | Vertex-grow + MinHeap |
| Time complexity | O(E log V) | O((V+E) log V) |
| Space | O(V) DSU | O(V + E) heap |
| Tốt cho | Sparse graph (E nhỏ) | Dense graph (E gần V²) |
| Disconnected graph | Trả minimum spanning forest | Chỉ xử lý 1 component từ vertex 0 |
| Input format tự nhiên | Edge list | Adjacency list |
Khi nào Kruskal thắng Prim?
Với sparse graph (E nhỏ hơn nhiều so với V²), O(E log V) của Kruskal tốt hơn O((V+E) log V) của Prim. Ví dụ: V = 1000, E = 2000 (sparse). Kruskal ≈ 2000 × 10 = 20.000 operations chủ yếu. Prim ≈ (1000 + 2000) × 10 = 30.000 — chậm hơn một chút, nhưng không đáng kể ở scale này.
Khi nào Prim thắng Kruskal?
Dense graph (E gần V²). Với E = V², Kruskal sort E edges = V² log V operations. Prim vẫn O((V + V²) log V) ≈ O(V² log V) — tương đương. Nhưng Prim có thể được optimize thêm bằng adjacency matrix thay heap, đạt O(V²) cho dense graph — thắng Kruskal rõ ràng.
7. Pitfall tổng hợp
Pitfall 1 — Kruskal trên disconnected graph trả spanning forest, không phải spanning tree
-- Graph có 2 component: {0,1,2} và {3,4}
-- Kruskal trả cạnh kết nối mỗi component riêng biệt
-- result.length = 3, không phải 4 (V-1)
-- Đây là minimum spanning FOREST -- đúng cho disconnected input
-- Nhưng caller phải kiểm tra result.length = n-1 để phát hiện graph không liên thông
if result.length < n - 1:
báo lỗi "Graph không liên thông"
Prim bắt đầu từ vertex 0 và chỉ xử lý component chứa vertex 0 — các component khác hoàn toàn bị bỏ qua.
Pitfall 2 — Prim heap chứa duplicate entries
-- Nhiều cạnh có thể dẫn đến cùng một vertex v -- tất cả được push vào heap
-- Khi poll entry (u->v, w): kiểm tra inTree[v] khi POLL, không khi PUSH
(u, v, w) <- H.pop()
if inTree[v]: continue -- lazy delete -- an toàn để bỏ qua
Đây là cùng pattern "lazy deletion" như Dijkstra (lesson 05). Push nhiều entries vào heap là bình thường và an toàn — chỉ cần kiểm tra khi poll. Heap có thể chứa tới O(E) entries — không ảnh hưởng correctness, chỉ tốn thêm space.
Pitfall 3 — Nhầm MST với shortest path tree (Dijkstra)
-- MST: tối ưu TỔNG weight của cây
-- Shortest path tree: tối ưu khoảng cách từ source đến TỪNG vertex riêng lẻ
--
-- Ví dụ đối chiếu (4 đỉnh):
-- Cạnh: 0->1 w1, 0->2 w3, 1->3 w1, 2->3 w1
--
-- MST: {(0,1,1), (1,3,1), (2,3,1)} -- tổng weight = 3
-- Dijkstra shortest path tree từ vertex 0:
-- dist[0]=0, dist[1]=1, dist[2]=3, dist[3]=2
-- Cạnh tree: {(0,1,1), (1,3,1), (0,2,3)} -- tổng weight = 5
--
-- Điểm mấu chốt: MST cạnh (2,3,1) = "kết nối vertex 2 vào cây rẻ nhất"
-- Dijkstra cạnh (0,2,3) = "đến vertex 2 từ nguồn 0 với chi phí nhỏ nhất"
-- Hai mục tiêu KHÁC NHAU
Không dùng Dijkstra để tìm MST — chúng giải hai bài toán khác nhau.
8. Variants và mở rộng
Borůvka's algorithm (1926): thuật toán MST đầu tiên được phát minh, trước cả Kruskal và Prim. Mỗi vòng lặp: mỗi component tìm edge nhỏ nhất ra ngoài component đó và thêm tất cả cùng lúc. Số component giảm xuống một nửa sau mỗi vòng → O(E log V) total. Đặc biệt phù hợp cho distributed MST — mỗi component có thể tìm min edge song song, không cần coordinator trung tâm.
Kruskal với lazy sort: thay vì sort toàn bộ edge list một lần, dùng min-heap để lấy edge nhỏ nhất theo từng bước. Useful khi chỉ cần một phần MST hoặc khi edge list quá lớn để sort toàn bộ cùng lúc.
MST verification: cho trước một spanning tree T, kiểm tra T có phải MST của G không trong O(V+E) — không cần chạy Kruskal/Prim lại. Dùng kỹ thuật cycle property: với mỗi non-tree edge e, nếu weight của e nhỏ hơn max weight trên path trong T giữa hai endpoint của e, thì T không phải MST. Linear verification algorithm của Dixon, Rauch, Tarjan (1992).
9. Ứng dụng thực tế
Network design — telco và datacenter: bài toán gốc của MST. Thiết kế mạng lưới cáp quang kết nối N điểm với chi phí tổng thể nhỏ nhất. Amazon AWS dùng MST-based algorithm để lên kế hoạch đặt cáp giữa các availability zone. Điều kiện thực tế phức tạp hơn (reliability, redundancy) — MST là baseline, sau đó thêm constraints.
Cluster analysis trong machine learning: hierarchical clustering với single-link method tương đương xây MST trên distance graph, rồi cắt bỏ các edge có weight lớn nhất. Số cluster = số cut. Scikit-learn AgglomerativeClustering(linkage='single') dùng MST internally cho large datasets.
Image segmentation trong computer vision: treat mỗi pixel là vertex, edge weight là difference giữa hai pixel liền kề. MST trên pixel graph xác định vùng ảnh liên kết theo màu/độ sáng. Felzenszwalb-Huttenlocher algorithm (2004) dùng MST variant để segment ảnh thời gian thực.
Approximation cho Traveling Salesman Problem: MST là lower bound cho TSP — bất kỳ TSP tour nào cũng có weight ít nhất bằng MST weight. Doubled MST (đi cạnh MST 2 lần) cho upper bound TSP × 2. Christofides algorithm (1976): MST + minimum weight perfect matching trên odd-degree vertices → tour với weight tối đa 1.5 × optimal TSP. Đây là approximation ratio tốt nhất đã biết cho general metric TSP trong nhiều thập kỷ — cho đến 2020, khi Karlin–Klein–Oveis Gharan công bố thuật toán phá ngưỡng 1.5 (đạt 1.5 − ε với ε rất nhỏ).
10. Deep Dive
Bài báo gốc:
- Kruskal, J.B. (1956) — "On the shortest spanning subtree of a graph and the traveling salesman problem", Proceedings of the AMS: bài báo 2 trang giới thiệu Kruskal's algorithm và kết nối với TSP.
- Prim, R.C. (1957) — "Shortest connection networks and some generalizations", Bell System Technical Journal: Prim's algorithm, tập trung vào dense graph và ứng dụng thiết kế mạng điện thoại.
- Borůvka, O. (1926) — "O jistém problému minimálním" (tiếng Czech): thuật toán MST đầu tiên, phát minh cho bài toán thiết kế mạng điện ở Moravia — 30 năm trước Kruskal và Prim.
Sách tham khảo:
- Introduction to Algorithms (CLRS), Chapter 23 — Minimum Spanning Trees: chứng minh đầy đủ cut property, loop property, correctness của Kruskal và Prim.
Cross-link trong khóa học:
- Module 3, Lesson 09 — Disjoint Set Union: chi tiết path compression, union by rank, proof O(α(n)) amortized — component mà Kruskal dùng như black box ở bài này.
- Thuật toán Căn bản — Module 2, Lesson 06 — MinHeap (Priority Queue): binary heap mà Prim phụ thuộc.
11. Tóm tắt
- MST là spanning tree (kết nối mọi vertex, V-1 edges, không cycle) với tổng edge weight nhỏ nhất. Unique nếu mọi weight khác nhau.
- Cut property: với bất kỳ cut (S, V-S) nào, edge nhỏ nhất qua cut phải thuộc ít nhất một MST — đây là nền tảng correctness cho cả Kruskal và Prim.
- Kruskal: sort tất cả edges tăng dần, thêm edge nếu không tạo cycle (dùng DSU), O(E log V). Tốt cho sparse graph và edge-list input.
- Prim: grow tree từ một vertex, MinHeap chứa edges ra ngoài tree, luôn pick edge nhỏ nhất, O((V+E) log V). Tốt cho dense graph và adjacency-list input.
- Lazy deletion trong Prim: push edge nhiều lần vào heap an toàn — kiểm tra
inTree[v]khi poll, không khi push. Tương tự pattern Dijkstra. - Disconnected graph: Kruskal trả minimum spanning forest (result.length nhỏ hơn V-1); Prim chỉ xử lý component của vertex 0.
- MST khác shortest path tree: MST tối ưu tổng weight toàn cây; Dijkstra tối ưu khoảng cách từ source đến từng vertex riêng lẻ — hai mục tiêu khác nhau.
- Ứng dụng: network design, ML clustering, image segmentation, TSP approximation (Christofides 1.5×).
12. Tự kiểm tra
Q1Cut property — vì sao edge nhỏ nhất qua một cut đảm bảo thuộc MST? Tại sao điều này chứng minh correctness cho cả Kruskal và Prim?▸
Cut property: với cut (S, V-S), gọi e là edge nhỏ nhất qua cut. Nếu e không thuộc MST T, thêm e vào T tạo đúng một cycle — cycle đó phải chứa edge f khác qua cùng cut. Vì weight(e) <= weight(f), thay f bằng e cho spanning tree có tổng weight không tăng — mâu thuẫn với T là MST tối ưu.
Kruskal correctness: mỗi edge được thêm là edge nhỏ nhất giữa hai component khác nhau — tương đương edge nhỏ nhất qua cut (component này, các component còn lại). Prim correctness: tại mỗi bước, cut là (tree-set, V - tree-set), và Prim luôn poll edge nhỏ nhất qua cut đó từ heap. Cả hai đều áp dụng cut property lặp đi lặp lại cho đến khi MST hoàn chỉnh.
Q2Kruskal O(E log V) vs Prim O((V+E) log V) — với sparse graph (E nhỏ hơn nhiều so với V²) và dense graph (E gần V²), nên chọn thuật toán nào? Ước lượng số operations.▸
Sparse graph (V=1000, E=2000): Kruskal ≈ E log V = 2000 × 10 = 20.000 operations chính. Prim ≈ (V+E) log V = 3000 × 10 = 30.000. Kruskal nhỉnh hơn — nhưng chênh lệch nhỏ. Kruskal thực sự tỏa sáng khi E rất nhỏ so với V.
Dense graph (V=1000, E=400.000 ≈ V²/2): Kruskal ≈ E log E = 400.000 × 19 ≈ 7.6 triệu. Prim ≈ (V+E) log V = 401.000 × 10 ≈ 4 triệu — Prim nhanh hơn gần 2 lần. Thêm vào đó, Prim với adjacency matrix (không dùng heap) đạt O(V²) = 1 triệu cho dense graph, thắng rõ ràng.
Q3Disconnected graph — Kruskal và Prim xử lý khác nhau thế nào? Làm sao detect graph disconnected?▸
Kruskal: tự nhiên trả minimum spanning forest — tập các MST của từng connected component. Kết thúc khi hết edges, không phải khi có đủ V-1 edges. Detect disconnect: sau khi chạy xong, kiểm tra result.length == n - 1. Nếu nhỏ hơn, graph có nhiều hơn một connected component.
Prim: chỉ explore component chứa vertex 0 (hoặc vertex bắt đầu). Các component khác hoàn toàn bị bỏ qua vì heap không bao giờ có edge tới chúng. Detect disconnect: result.length < n - 1 sau khi heap rỗng. Để tìm MST của toàn bộ forest với Prim, cần chạy Prim nhiều lần — một lần cho mỗi unvisited vertex.
Q4Prim heap có nhiều duplicate entries — vì sao push edge nhiều lần vào heap vẫn an toàn? So sánh với lazy deletion trong Dijkstra.▸
Khi expand vertex u, tất cả edges từ u tới neighbor chưa trong tree được push vào heap. Nếu sau đó vertex v được thêm vào tree qua con đường khác, các entries (u→v, weight) cũ trong heap trở thành "stale". Khi poll entry đó: if inTree[v]: continue — skip ngay, không xử lý tiếp.
Pattern giống hệt lazy deletion trong Dijkstra: thay vì xóa entry khỏi heap (heap thường không support decrease-key), ta push entry mới và kiểm tra validity khi poll. Correctness vì mọi vertex chỉ được thêm vào tree một lần — mọi entry stale đều bị skip. Cost: heap có thể chứa O(E) entries thay O(V), nhưng log E = O(log V) nên complexity tổng không thay đổi.
Q5Cho graph 4 vertex: edges (0,1,w2), (0,2,w4), (1,2,w1), (1,3,w5), (2,3,w3). MST và shortest path tree từ vertex 0 có khác nhau không? Giải thích sự khác biệt.▸
MST (Kruskal): sort edges: (1,2,w1),(0,1,w2),(2,3,w3),(0,2,w4),(1,3,w5). Thêm (1,2,w1) — không cycle. Thêm (0,1,w2) — không cycle. Thêm (2,3,w3) — không cycle. V-1=3 edges xong. MST = { (1,2,1), (0,1,2), (2,3,3) }, tổng = 6.
Shortest path tree từ 0 (Dijkstra): dist[0]=0, dist[1]=2, dist[2]=3 (qua 1), dist[3]=6 (qua 1→2→3). Tree edges: { (0,1,2), (1,2,1), (2,3,3) }, tổng = 6. Trường hợp này kết quả trùng nhau về edge set. Nhưng mục tiêu khác: MST tối ưu tổng chi phí kết nối toàn bộ graph; Dijkstra tối ưu khoảng cách từ source 0 tới từng vertex. Trên nhiều graph khác hai kết quả sẽ khác nhau — đặc biệt khi có edge nối thẳng với weight lớn từ source mà MST không cần.
Q6DSU trong Kruskal — path compression và union by rank giúp gì cho complexity? Nếu không dùng hai optimization này thì sao?▸
Không có optimization: find(x) phải đi lên từng bước đến root — worst case O(V) nếu cây DSU degenerates thành linked list (ví dụ luôn union theo một chiều nhất định). Tổng E lần union × O(V) mỗi lần = O(V × E) chỉ cho DSU operations — toàn bộ Kruskal thành O(V × E + E log E), chậm hơn nhiều.
Path compression: khi find(x) đi lên root, gán trực tiếp parent[x] = root cho mọi node trên đường đi. Các lần find sau trên cùng node mất O(1). Union by rank: luôn attach cây thấp hơn vào cây cao hơn — giữ chiều cao cây O(log V). Kết hợp hai optimization: amortized O(α(V)) per operation, α là inverse Ackermann — tăng cực kỳ chậm (α(10^80) = 4), thực tế coi như O(1). Toàn bộ Kruskal đạt O(E log V).
Bài tiếp theo: Disjoint Set Union — path compression, union by rank
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