Menu
CoddyTech

Thuật toán Kruskal

Cập nhật lần cuối

Thuật toán Kruskal xây dựng cây khung nhỏ nhất (minimum spanning tree, MST), tức là tập cạnh có tổng trọng số nhỏ nhất nối tất cả các đỉnh mà không tạo chu trình. Thuật toán sắp xếp mọi cạnh theo trọng số, rồi tham lam thêm cạnh rẻ nhất tiếp theo, miễn là nó nối hai đỉnh chưa được kết nối với nhau. Nhấn Phát ở trên để xem cây lớn dần, mỗi lần thêm một cạnh an toàn rẻ nhất.

Việc kiểm tra "đã được kết nối chưa?" dùng cấu trúc dữ liệu union-find (tập rời rạc, disjoint set): một cạnh bị bỏ qua nếu hai đầu mút của nó đã nằm trong cùng một thành phần, vì thêm nó vào sẽ tạo chu trình. Bước sắp xếp chiếm phần lớn chi phí, cho tổng độ phức tạp O(E log E). Kruskal phát huy thế mạnh trên đồ thị thưa.

Độ phức tạp thời gian và không gian

Đại lượngĐộ phức tạpGhi chú
Thời gianO(E log E)Chủ yếu do sắp xếp các cạnh
Thao tác union-find≈ O(E α(V))Gần như hằng số mỗi lần kiểm tra nhờ nén đường đi
Không gianO(V + E)Danh sách cạnh và cấu trúc tập rời rạc
Phù hợp nhất vớiĐồ thị thưaLàm việc trên một danh sách cạnh chung

Từng bước

BướcĐiều gì xảy ra
1Sắp xếp mọi cạnh theo trọng số tăng dần.
2Đặt mỗi đỉnh vào một thành phần riêng (union-find).
3Lấy cạnh rẻ nhất tiếp theo.
4Nếu hai đầu mút nằm ở hai thành phần khác nhau, thêm cạnh vào cây và hợp nhất hai thành phần.
5Nếu không, bỏ qua cạnh đó (nó sẽ tạo chu trình).
6Dừng khi cây có V − 1 cạnh.

Ví dụ minh họa

Đồ thị có các đỉnh A, B, C, D và các cạnh A-B(1), B-C(2), A-C(3), C-D(4), B-D(5). Các cạnh sau khi sắp xếp: A-B(1), B-C(2), A-C(3), C-D(4), B-D(5):

Cạnh (trọng số)Các thành phần trước đóThao tác
A-B(1){A} {B} {C} {D}Khác thành phần: thêm vào MST, hợp nhất thành {A,B}.
B-C(2){A,B} {C} {D}Khác thành phần: thêm vào MST, hợp nhất thành {A,B,C}.
A-C(3){A,B,C} {D}A và C đã cùng thành phần: bỏ qua (sẽ tạo chu trình).
C-D(4){A,B,C} {D}Khác thành phần: thêm vào MST, hợp nhất thành {A,B,C,D}.
Dừng{A,B,C,D}Cây có V - 1 = 3 cạnh. MST = A-B, B-C, C-D, tổng trọng số 7.

Khi nào nên dùng thuật toán Kruskal

Nên dùng khiTránh dùng khi
Đồ thị thưa (ít cạnh so với số đỉnh).Đồ thị dày: Prim dùng heap thường nhanh hơn.
Các cạnh đã có sẵn dưới dạng một danh sách chung để sắp xếp.Các cạnh chỉ có trong danh sách kề và phải duyệt theo từng đỉnh.
Bạn muốn một cách cài đặt đơn giản dựa trên union-find.Bạn cần cây mọc ra từ một đỉnh xuất phát cụ thể.
Bạn muốn xây dựng rừng khung của một đồ thị không liên thông.Bạn phải xử lý các cạnh đến liên tục mà không sắp xếp toàn bộ.

Code Kruskal's Algorithm

Một cách cài đặt Kruskal's Algorithm gọn gàng, chạy được ngay bằng Python, JavaScript, Java, C++, C. Chọn một ngôn ngữ, sao chép code hoặc mở sẵn trong Coddy Playground.

Code Kruskal's Algorithm bằng Python

Python
1def find(parent, x):2    while parent[x] != x:3        parent[x] = parent[parent[x]]  # path compression4        x = parent[x]5    return x6
7
8def kruskal(vertices, edges):9    # Take the cheapest edge that does not close a cycle10    parent = {v: v for v in vertices}11    mst, total = [], 012    for w, u, v in sorted(edges):13        root_u, root_v = find(parent, u), find(parent, v)14        if root_u != root_v:15            parent[root_u] = root_v16            mst.append((u, v, w))17            total += w18    return mst, total19
20
21vertices = ["A", "B", "C", "D", "E"]22edges = [23    (4, "A", "B"), (1, "A", "C"), (3, "B", "C"), (2, "B", "D"),24    (5, "C", "D"), (6, "C", "E"), (7, "D", "E"),25]26
27mst, total = kruskal(vertices, edges)28for u, v, w in mst:29    print(f"{u} - {v} (weight {w})")30print("Total MST weight:", total)
Chạy code này trong Playground Python

Câu hỏi thường gặp về thuật toán Kruskal

Cây khung nhỏ nhất là gì?
Cây khung nhỏ nhất (MST) của một đồ thị có trọng số liên thông là một tập con các cạnh nối tất cả các đỉnh, không có chu trình và có tổng trọng số nhỏ nhất có thể. Với V đỉnh, nó có đúng V - 1 cạnh.
Thuật toán Kruskal và Prim khác nhau như thế nào?

Cả hai đều xây dựng cây khung nhỏ nhất theo kiểu tham lam. Kruskal sắp xếp mọi cạnh trên toàn đồ thị và thêm cạnh rẻ nhất không tạo chu trình, dùng union-find. Prim cho một cây duy nhất mọc ra từ đỉnh xuất phát, luôn thêm cạnh rẻ nhất đi ra khỏi cây. Kruskal hợp với đồ thị thưa; Prim (dùng heap) hợp với đồ thị dày.

Tại sao thuật toán Kruskal dùng union-find?

Trước khi thêm một cạnh, Kruskal phải kiểm tra hai đầu mút của nó đã được kết nối chưa, vì thêm cạnh như vậy sẽ tạo chu trình. Union-find (tập rời rạc) trả lời câu hỏi "hai đỉnh này có cùng thành phần không?" và hợp nhất các thành phần trong thời gian khấu hao gần như hằng số, giúp thuật toán luôn hiệu quả.

Khi nào nên dùng thuật toán Kruskal thay vì Prim?
Hãy chọn Kruskal khi đồ thị thưa và bạn đã có sẵn mọi cạnh dưới dạng danh sách để sắp xếp: bước sắp xếp O(E log E) rẻ khi E nhỏ. Thuật toán Prim dùng binary heap hoặc Fibonacci heap thường thắng trên đồ thị dày, khi E tiến gần V², vì nó không phải sắp xếp mọi cạnh ngay từ đầu.
Thuật toán Kruskal có chạy được trên đồ thị không liên thông không?
Có. Nếu đồ thị không liên thông, Kruskal đơn giản là không thể đạt V - 1 cạnh và thay vào đó tạo ra một rừng khung nhỏ nhất, mỗi thành phần liên thông có một MST. Điều này xảy ra tự nhiên vì union-find không bao giờ hợp nhất những đỉnh không có đường đi giữa chúng.
Lỗi thường gặp nhất khi cài đặt thuật toán Kruskal là gì?

Bỏ qua kỹ thuật nén đường đi (path compression) và hợp nhất theo hạng (union by rank) của union-find, khiến mỗi lần kiểm tra kết nối từ gần như hằng số trở thành gần như tuyến tính và có thể chiếm phần lớn thời gian chạy. Một bug phổ biến khác là quên hợp nhất hai thành phần sau khi thêm cạnh, khiến các cạnh sau có thể tạo chu trình.

Hình minh họa các ngôn ngữ lập trình của Coddy

Thành thạo thuật toán cùng Coddy

Bắt đầu