Menu
CoddyTech

Thuật toán Prim

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

Thuật toán Prim 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 mọi đỉnh mà không tạo chu trình, bằng cách cho một cây duy nhất mọc dần ra từ một đỉnh xuất phát. Ở mỗi bước, thuật toán xem xét mọi cạnh đi từ cây sang một đỉnh nằm ngoài cây và thêm cạnh rẻ nhất. Nhấn Phát ở trên để xem cây mở rộng, mỗi lần lấy cạnh có trọng số nhỏ nhất dẫn đến một đỉnh mới.

Vì luôn chọn cạnh cắt nhỏ nhất, mỗi lần thêm đều an toàn (chắc chắn thuộc về một MST nào đó). Với hàng đợi ưu tiên dùng binary heap, lưu cạnh rẻ nhất đến mỗi đỉnh bên ngoài, Prim chạy trong O(E log V). Thuật toán này khác với Kruskal, vốn sắp xếp mọi cạnh trên toàn đồ thị thay vì cho một cây liên thông mọc dần.

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

Cách cài đặtĐộ phức tạpGhi chú
Binary heapO(E log V)Hàng đợi ưu tiên chứa các cạnh cắt
Ma trận kềO(V²)Đơn giản hơn; tốt cho đồ thị dày
Không gianO(V + E)Đánh dấu đỉnh trong cây và hàng đợi ưu tiên
Phù hợp nhất vớiĐồ thị dàyMọc ra từ một đỉnh xuất phát

Từng bước

BướcĐiều gì xảy ra
1Bắt đầu cây với một đỉnh bất kỳ.
2Xem xét mọi cạnh đi từ cây sang một đỉnh nằm ngoài cây.
3Chọn cạnh cắt có trọng số nhỏ nhất.
4Thêm cạnh đó và đỉnh mới của nó vào cây.
5Lặp lại cho đến khi mọi đỉnh đều nằm trong cây.

Ví dụ minh họa

Xây dựng MST của một đồ thị 4 đỉnh với các cạnh A-B=1, A-C=3, B-C=2, B-D=4, C-D=5, xuất phát từ A:

BướcCâyCác cạnh cắtCạnh được chọn
1{A}A-B=1, A-C=3A-B (trọng số 1)
2{A, B}A-C=3, B-C=2, B-D=4B-C (trọng số 2)
3{A, B, C}B-D=4, C-D=5B-D (trọng số 4)
4{A, B, C, D}không còn: mọi đỉnh đã ở trong câyxong: trọng số MST 1+2+4 = 7

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

Nên dùng khiTránh dùng khi
Bạn cần cây khung nhỏ nhất của một đồ thị vô hướng, có trọng số và liên thông.Đồ thị có hướng hoặc bạn cần đường đi ngắn nhất: hãy dùng Dijkstra hoặc Bellman-Ford.
Đồ thị dày (E gần bằng V²); dạng ma trận O(V²) vừa đơn giản vừa nhanh.Đồ thị thưa và các cạnh đã được sắp xếp hoặc dễ sắp xếp: Kruskal thường đơn giản hơn.
Bạn muốn cây mọc dần ra từ một vùng (ví dụ bố trí mạng theo từng bước).Đồ thị không liên thông: Prim chỉ phủ một thành phần, trong khi bạn cần rừng khung nhỏ nhất.
Bạn đã có sẵn cấu trúc kề và một hàng đợi ưu tiên.Bạn cần phát hiện chu trình trên toàn bộ tập cạnh: union-find (Kruskal) phù hợp hơn với bài toán đó.

Code Prim's Algorithm

Một cách cài đặt Prim'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 Prim's Algorithm bằng Python

Python
1import heapq2
3
4def prim(graph, start):5    visited = {start}6    heap = [(w, start, v) for v, w in graph[start]]7    heapq.heapify(heap)8    mst, total = [], 09    while heap and len(visited) < len(graph):10        w, u, v = heapq.heappop(heap)11        if v in visited:12            continue13        visited.add(v)14        mst.append((u, v, w))15        total += w16        # Offer the new node's edges to the frontier17        for neighbor, weight in graph[v]:18            if neighbor not in visited:19                heapq.heappush(heap, (weight, v, neighbor))20    return mst, total21
22
23graph = {24    "A": [("B", 4), ("C", 1)],25    "B": [("A", 4), ("C", 3), ("D", 2)],26    "C": [("A", 1), ("B", 3), ("D", 5)],27    "D": [("B", 2), ("C", 5), ("E", 7)],28    "E": [("D", 7)],29}30
31mst, total = prim(graph, "A")32for u, v, w in mst:33    print(f"{u} - {v} (weight {w})")34print("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 Prim

Độ phức tạp thời gian của thuật toán Prim là bao nhiêu?
Với hàng đợi ưu tiên dùng binary heap, Prim chạy trong O(E log V). Phiên bản ma trận kề đơn giản hơn, mỗi bước quét tìm cạnh cắt nhỏ nhất, tốn O(V²) và có thể nhanh hơn trên đồ thị dày. Cả hai đều dùng O(V + E) bộ nhớ.
Thuật toán Prim và Kruskal khác nhau như thế nào?

Cả hai đều tìm cây khung nhỏ nhất theo kiểu tham lam. Prim cho một cây liên thông mọc ra từ đỉnh xuất phát, lặp lại việc thêm cạnh rẻ nhất đi ra khỏi cây (dùng hàng đợi ưu tiên). Kruskal xét mọi cạnh theo trọng số tăng dần và thêm cạnh rẻ nhất không tạo chu trình (dùng union-find). Prim thường được ưu tiên trên đồ thị dày, Kruskal trên đồ thị thưa.

Thuật toán Prim có luôn tìm được MST tối ưu không?

Có. Ở mỗi bước, cạnh rẻ nhất cắt qua cây hiện tại luôn an toàn để thêm vào, vì nó thuộc về một cây khung nhỏ nhất nào đó (tính chất lát cắt). Lặp lại việc thêm các cạnh an toàn sẽ cho ra một cây khung nhỏ nhất thực sự, miễn là đồ thị liên thông.

Khi nào nên dùng thuật toán Prim thay vì Kruskal?
Hãy chọn Prim khi đồ thị dày, vì dạng ma trận kề O(V²) của nó không phải sắp xếp cả E cạnh và cho một cây mọc ra từ đỉnh xuất phát. Kruskal phát huy thế mạnh trên đồ thị thưa, nơi sắp xếp danh sách cạnh rẻ và union-find giữ cho việc kiểm tra chu trình nhanh. Cả hai đều cho ra MST đúng, nên lựa chọn chủ yếu phụ thuộc vào mật độ cạnh và các cấu trúc dữ liệu bạn đã có.
Đỉnh xuất phát có ảnh hưởng đến MST thu được không?

Không. Tổng trọng số của MST là như nhau dù bạn xuất phát từ đỉnh nào, vì trọng số cây khung nhỏ nhất của một đồ thị liên thông là duy nhất. Các cạnh cụ thể chỉ có thể khác nhau khi nhiều cạnh có cùng trọng số và tồn tại nhiều MST. Ngoài trường hợp đó, Prim luôn cho ra đúng cùng một cây bất kể đỉnh xuất phát.

Thuật toán Prim có xử lý được đồ thị không liên thông không?

Không trực tiếp. Prim cho một cây duy nhất mọc dần, nên nó chỉ phủ thành phần liên thông chứa đỉnh xuất phát và bỏ qua phần còn lại. Để phủ mọi thành phần, bạn phải chạy lại Prim từ một đỉnh chưa được thăm nhiều lần, tạo ra một rừng khung nhỏ nhất thay vì một cây.

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