Cây khung nhỏ nhất (MST)
Định nghĩa
Trước khi đọc phần này, bạn nên đọc Các khái niệm cơ bản về đồ thị và Cây cơ bản, đồng thời nắm được các định nghĩa sau:
- Đồ thị con sinh (subgraph)
- Cây khung (spanning tree)
Ta định nghĩa Cây khung nhỏ nhất (Minimum Spanning Tree, MST) của một đồ thị vô hướng liên thông là cây khung có tổng trọng số các cạnh nhỏ nhất.
Lưu ý: Chỉ đồ thị liên thông mới có cây khung, còn với đồ thị không liên thông thì chỉ tồn tại rừng khung (spanning forest).
Thuật toán Kruskal
Thuật toán Kruskal là một trong những thuật toán tìm cây khung nhỏ nhất phổ biến và dễ cài đặt, do Kruskal phát minh. Ý tưởng cơ bản là thêm các cạnh theo thứ tự tăng dần trọng số, đây là một thuật toán tham lam (greedy).
Kiến thức nền tảng
DSU - Hợp nhất tập hợp (Union-Find), Tham lam (Greedy), Lưu trữ đồ thị.
Cài đặt
Minh họa:
Giả mã:
Thuật toán đơn giản nhưng cần cấu trúc dữ liệu phù hợp... Cụ thể, cần duy trì một rừng, kiểm tra hai đỉnh có cùng cây không, và hợp nhất hai cây.
Trừu tượng hơn, cần duy trì nhiều tập hợp, kiểm tra hai phần tử có cùng tập hợp không, và hợp nhất hai tập hợp.
Việc kiểm tra hai đỉnh liên thông và hợp nhất hai đỉnh có thể dùng DSU (Disjoint Set Union).
Nếu dùng thuật toán sắp xếp \(O(m\log m)\) và DSU \(O(m\alpha(m, n))\) hoặc \(O(m\log n)\), ta đạt độ phức tạp \(O(m\log m)\) cho Kruskal.
Chứng minh
Ý tưởng đơn giản: Để xây dựng cây khung nhỏ nhất, ta bắt đầu từ cạnh nhỏ nhất, thêm dần các cạnh theo thứ tự tăng dần trọng số, nếu thêm cạnh tạo thành chu trình thì bỏ qua, cho đến khi có đủ \(n-1\) cạnh.
Chứng minh: Dùng quy nạp, chứng minh tại mọi thời điểm, tập cạnh mà Kruskal chọn đều nằm trong một cây khung nhỏ nhất nào đó.
Cơ sở: Ban đầu, rõ ràng đúng (cây khung nhỏ nhất tồn tại).
Bước quy nạp: Giả sử đúng tại thời điểm hiện tại, tập cạnh là \(F\), \(T\) là một cây khung nhỏ nhất chứa \(F\), xét cạnh tiếp theo \(e\).
Nếu \(e\) thuộc \(T\), hiển nhiên đúng.
Nếu không, \(T+e\) tạo thành một chu trình, xét cạnh \(f\) trên chu trình này không thuộc \(F\) (tồn tại ít nhất một cạnh như vậy).
Trọng số \(f\) không nhỏ hơn \(e\), nếu nhỏ hơn thì \(f\) đã được chọn trước \(e\).
Trọng số \(f\) cũng không lớn hơn \(e\), nếu lớn hơn thì \(T+e-f\) là cây khung tốt hơn \(T\) (mâu thuẫn).
Vậy \(T+e-f\) vẫn là cây khung nhỏ nhất chứa \(F\), quy nạp thành công.
Bài mẫu
洛谷 P1195 Bầu trời trong túi
Có \(n\) đám mây, bạn cần nối chúng thành \(k\) viên kẹo bông, nối đám mây \(X_i\) và \(Y_i\) tốn \(L_i\), hỏi tổng chi phí nhỏ nhất.
Code mẫu
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 | |
Thuật toán Prim
Thuật toán Prim là một thuật toán phổ biến khác để tìm cây khung nhỏ nhất. Ý tưởng là bắt đầu từ một đỉnh, liên tục thêm đỉnh mới (khác với Kruskal là thêm cạnh).
Cài đặt
Minh họa:
Cụ thể, mỗi lần chọn đỉnh có khoảng cách nhỏ nhất tới tập đã chọn, và dùng các cạnh mới cập nhật khoảng cách các đỉnh còn lại.
Thực chất giống thuật toán Dijkstra: mỗi lần chọn đỉnh có khoảng cách nhỏ nhất, có thể tìm bằng vét cạn hoặc dùng heap.
Nếu dùng heap nhị phân (không hỗ trợ decrease-key \(O(1)\)), độ phức tạp không tốt hơn Kruskal, và hằng số cũng lớn hơn. Thông thường, Kruskal được ưu tiên hơn, nhưng với đồ thị dày đặc (đặc biệt là đồ thị đầy đủ), Prim vét cạn có thể nhanh hơn Kruskal, nhưng không chắc chạy nhanh hơn thực tế.
Vét cạn: \(O(n^2+m)\).
Heap nhị phân: \(O((n+m) \log n)\).
Heap Fibonacci: \(O(n \log n + m)\).
Giả mã:
Lưu ý: Đoạn code trên chỉ tính tổng trọng số cây khung nhỏ nhất, nếu muốn in ra phương án cần lưu lại cạnh ứng với mỗi \(dis\).
Code mẫu
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | |
Chứng minh
Bắt đầu từ một đỉnh bất kỳ, chia các đỉnh thành hai loại: đã chọn và chưa chọn.
Mỗi lần chọn đỉnh chưa chọn có cạnh nối với tập đã chọn có trọng số nhỏ nhất.
Sau đó thêm đỉnh này vào, nối bằng cạnh nhỏ nhất.
Lặp lại \(n-1\) lần.
Chứng minh: Tại mỗi bước, luôn tồn tại một cây khung nhỏ nhất chứa tập cạnh đã chọn.
Cơ sở: Khi chỉ có một đỉnh, hiển nhiên đúng.
Bước quy nạp: Nếu đúng ở bước hiện tại, tập cạnh là \(F\), thuộc cây khung nhỏ nhất \(T\), xét cạnh \(e\) sắp thêm.
Nếu \(e\) thuộc \(T\), hiển nhiên đúng.
Nếu không, xét chu trình \(T+e\) và cạnh \(f\) khác có thể thay thế.
Trọng số \(f\) không nhỏ hơn \(e\), nếu nhỏ hơn thì đã chọn \(f\) trước.
Trọng số \(f\) cũng không lớn hơn \(e\), nếu lớn hơn thì \(T+e-f\) là cây khung tốt hơn \(T\).
Vậy \(e\) và \(f\) bằng nhau, \(T+e-f\) vẫn là cây khung nhỏ nhất chứa \(F\).
Thuật toán Boruvka
Tiếp theo là một thuật toán khác để tìm cây khung nhỏ nhất: Boruvka. Ý tưởng là kết hợp hai thuật toán trên. Boruvka có thể dùng để tìm rừng khung nhỏ nhất cho đồ thị vô hướng (đồ thị liên thông thì là cây khung nhỏ nhất).
Với các bài toán có tính chất đặc biệt về cạnh, Boruvka có ưu thế, ví dụ bài CF888G với đồ thị đầy đủ.
Một số định nghĩa:
- Gọi \(E'\) là tập cạnh của rừng khung nhỏ nhất hiện tại. Trong quá trình thuật toán, ta dần thêm cạnh vào \(E'\). Khối liên thông là tập đỉnh \(V'\subseteq V\) sao cho mọi cặp \(u,v\) trong \(V'\) liên thông qua các cạnh trong \(E'\).
- Cạnh nhỏ nhất của một khối liên thông là cạnh nối khối đó với khối khác có trọng số nhỏ nhất.
Ban đầu, \(E'=\varnothing\), mỗi đỉnh là một khối liên thông riêng:
- Xác định mỗi đỉnh thuộc khối liên thông nào. Đặt mỗi khối là "chưa có cạnh nhỏ nhất".
- Duyệt từng cạnh \((u, v)\), nếu \(u\) và \(v\) khác khối, dùng cạnh này cập nhật cạnh nhỏ nhất của hai khối.
- Nếu mọi khối đều "chưa có cạnh nhỏ nhất", dừng thuật toán, \(E'\) là rừng khung nhỏ nhất. Ngược lại, thêm các cạnh nhỏ nhất của từng khối vào \(E'\), quay lại bước 1.
Minh họa động (nguồn: Wikipedia):
Nếu đồ thị liên thông, mỗi vòng lặp số khối liên thông giảm ít nhất một nửa, nên thuật toán lặp tối đa \(O(\log V)\) lần. Nếu không liên thông thì là nhiều bài toán con. Độ phức tạp \(O(E\log V)\). Giả mã (chỉnh sửa từ Wikipedia):
Lưu ý: Khi so sánh cạnh, nên dùng thêm tiêu chí phụ (ví dụ chỉ số cạnh) để phân biệt khi trọng số bằng nhau.
Bài tập
Tính duy nhất của cây khung nhỏ nhất
Xét tính duy nhất của cây khung nhỏ nhất. Nếu một cạnh không thuộc cây khung nhỏ nhất, nhưng có thể thay thế cho một cạnh cùng trọng số, thuộc cây khung nhỏ nhất, thì cây khung nhỏ nhất là không duy nhất.
Với Kruskal, chỉ cần đếm số cạnh cùng trọng số có thể chọn và số cạnh thực sự được chọn. Nếu hai số này khác nhau, tức là các cạnh này cùng tạo thành một chu trình (trong chu trình có ít nhất hai cạnh cùng trọng số), tức là cây khung nhỏ nhất không duy nhất.
Để tìm các cạnh cùng trọng số, chỉ cần lưu lại chỉ số đầu/cuối, dùng hàng đợi đơn điệu là có thể giải quyết trong \(O(\alpha(m))\) (với \(m\) là số cạnh), gần như không tăng độ phức tạp so với thuật toán gốc.
Bài mẫu: POJ 1679
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | |
Cây khung nhỏ thứ hai
Cây khung nhỏ thứ hai không chặt (non-strict)
Định nghĩa
Trong đồ thị vô hướng, cây khung có tổng trọng số nhỏ nhất lớn hơn hoặc bằng cây khung nhỏ nhất.
Cách tìm
- Tìm cây khung nhỏ nhất \(T\), tổng trọng số \(M\).
- Duyệt từng cạnh chưa chọn \(e = (u,v,w)\), tìm trên \(T\) cạnh lớn nhất trên đường từ \(u\) đến \(v\) là \(e' = (s,t,w')\), thay \(e'\) bằng \(e\) được cây khung mới \(T'\) có tổng trọng số \(M' = M + w - w'\).
- Lấy giá trị nhỏ nhất trong các \(M'\).
Làm sao tìm cạnh lớn nhất trên đường \(u,v\)? Dùng kỹ thuật "bậc thang" (binary lifting), tiền xử lý tổ tiên \(2^i\) của mỗi đỉnh và trọng số lớn nhất trên đường đó, khi tìm LCA thì truy vấn luôn được.
Cây khung nhỏ thứ hai chặt (strict)
Định nghĩa
Trong đồ thị vô hướng, cây khung có tổng trọng số nhỏ nhất lớn hơn cây khung nhỏ nhất.
Cách tìm
Ở cách trên, vì cây khung nhỏ nhất đảm bảo cạnh lớn nhất trên đường \(u\) đến \(v\) không lớn hơn các đường khác, nên nếu cạnh thay thế có trọng số bằng cạnh bị thay, kết quả là cây khung nhỏ thứ hai không chặt.
Cách giải quyết: Khi tiền xử lý tổ tiên \(2^i\), ngoài trọng số lớn nhất còn lưu trọng số lớn thứ hai (nếu có). Khi thay thế, nếu trọng số cạnh thay bằng trọng số lớn nhất, thì dùng trọng số lớn thứ hai.
Có thể dùng binary lifting, độ phức tạp \(O(m \log m)\).
Code mẫu
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 | |
Cây khung nút thắt (Bottleneck Spanning Tree)
Định nghĩa
Cây khung nút thắt của đồ thị vô hướng \(G\) là cây khung mà trọng số cạnh lớn nhất là nhỏ nhất trong tất cả các cây khung của \(G\).
Tính chất
Cây khung nhỏ nhất luôn là cây khung nút thắt, nhưng ngược lại thì không chắc. Có thể chứng minh bằng phản chứng: Nếu cây khung nhỏ nhất có cạnh lớn nhất là \(w\), giả sử tồn tại cây khung nút thắt mà mọi cạnh đều nhỏ hơn \(w\), thì thay cạnh lớn nhất của cây khung nhỏ nhất bằng một cạnh trong cây khung nút thắt sẽ được cây khung có tổng trọng số nhỏ hơn, mâu thuẫn.
Bài mẫu
POJ 2395 Out of Hay
Cho \(n\) trang trại và \(m\) cạnh, các trang trại đánh số \(1\) đến \(n\). Một người xuất phát từ trang trại \(1\) đến các trang trại khác, hỏi trên đường đi anh ta cần mang nhiều nhất bao nhiêu nước (mỗi lần đến trang trại có thể tiếp nước, tổng đường đi phải nhỏ nhất). Bài này yêu cầu cạnh lớn nhất trên cây khung nhỏ nhất.
Đường đi nút thắt nhỏ nhất (Minimum Bottleneck Path)
Định nghĩa
Trong đồ thị vô hướng \(G\), đường đi nút thắt nhỏ nhất từ \(x\) đến \(y\) là đường đi đơn giản mà cạnh lớn nhất trên đường đi là nhỏ nhất trong tất cả các đường đi từ \(x\) đến \(y\).
Tính chất
Theo định nghĩa cây khung nhỏ nhất, cạnh lớn nhất trên đường đi từ \(x\) đến \(y\) trên cây khung nhỏ nhất chính là giá trị nhỏ nhất có thể. Dù cây khung nhỏ nhất không duy nhất, nhưng trên mọi cây khung nhỏ nhất, giá trị này đều như nhau.
Tuy nhiên, không phải mọi đường đi nút thắt nhỏ nhất đều là đường đi trên cây khung nhỏ nhất.
Ví dụ:

Đường đi nút thắt nhỏ nhất từ \(1\) đến \(4\) có hai đường: 1-2-3-4 và 1-3-4.
Nhưng cạnh 1-2 không xuất hiện trong bất kỳ cây khung nhỏ nhất nào.
Ứng dụng
Vì đường đi nút thắt nhỏ nhất không duy nhất, thường chỉ hỏi giá trị cạnh lớn nhất trên đường đi đó.
Tức là, cần truy vấn max trên đường đi trong cây khung nhỏ nhất.
Có thể dùng binary lifting, HLD,... để giải quyết.
Cây tái cấu trúc Kruskal
Định nghĩa
Trong quá trình chạy Kruskal, ta thêm các cạnh theo thứ tự tăng dần trọng số.
Ban đầu tạo \(n\) tập hợp, mỗi tập có một đỉnh, trọng số \(0\).
Mỗi lần thêm cạnh, hợp nhất hai tập hợp, tạo một đỉnh mới có trọng số bằng trọng số cạnh vừa thêm, hai gốc của hai tập hợp làm con trái/phải của đỉnh mới. Sau đó hợp nhất hai tập và đỉnh mới thành một tập, đỉnh mới là gốc.
Sau \(n-1\) lần, ta được một cây nhị phân có đúng \(n\) lá, mỗi nút trong có hai con. Đó là cây tái cấu trúc Kruskal.
Ví dụ:

Cây tái cấu trúc Kruskal của đồ thị trên:

Tính chất
Dễ thấy, giá trị nhỏ nhất của cạnh lớn nhất trên mọi đường đi đơn giản giữa hai đỉnh \(x, y\) trong đồ thị gốc = max cạnh trên đường đi giữa \(x, y\) trên cây khung nhỏ nhất = giá trị tại LCA của \(x, y\) trên cây tái cấu trúc Kruskal.
Tức là, tập các đỉnh \(y\) sao cho max cạnh trên đường từ \(x\) đến \(y\) không vượt quá \(val\) chính là tập lá của một cây con trên cây tái cấu trúc Kruskal.
Để tìm tập này, chỉ cần tìm trên cây tái cấu trúc Kruskal đỉnh tổ tiên nông nhất trên đường từ \(x\) lên gốc có trọng số \(\leq val\).
Nếu muốn tìm min cạnh lớn nhất trên mọi đường đi đơn giản giữa hai đỉnh, thì chạy Kruskal theo thứ tự giảm dần trọng số.
「LOJ 137」Đường đi nút thắt nhỏ nhất - bản mở rộng
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 | |
NOI 2018 Quy trình trở về
Đầu tiên tiền xử lý đường đi ngắn nhất từ mỗi đỉnh đến gốc.
Xây dựng cây khung lớn nhất theo độ cao. Rõ ràng, mỗi truy vấn tìm các đỉnh có thể đến được là các đỉnh trên đường đi từ đỉnh truy vấn đến gốc trên cây khung lớn nhất có trọng số cạnh nhỏ nhất \(> p\).
Theo tính chất cây tái cấu trúc Kruskal, các đỉnh này là tập lá của một cây con.
Chỉ cần truy vấn min trọng số lá trong cây con đó.
Có thể tìm gốc cây con bằng binary lifting trên cây tái cấu trúc Kruskal.
Độ phức tạp \(O((n+m+Q) \log n)\).
````
Last updated on this page:, Update history
Found an error? Want to help improve? Edit this page on GitHub!
Contributors to this page:Chrogeek, Enter-tainer, HeRaNO, Ir1d, Marcythm, ShadowsEpic, StudyingFather, Xeonacid, bear-good, billchenchina, diauweb, diauweb, greyqz, kawa-yoiko, ouuan, partychicken, sshwy, stevebraveman, zhouyuyang2002, renbaoshuo, Hszzzx, y-kx-b, toprise
All content on this page is provided under the terms of the CC BY-SA 4.0 and SATA license, additional terms may apply