đầu vì U chỉ chứa một đỉnh s, nên với mọi v # s ta khởi tạo key|v| = c(s.V). và near[v] = s. Sau đó trong mỗi bước lặp của thuật toán Prim, ta loại đỉnh v có khoá nhỏ nhất khởi hàng ưu tiên và thêm cạnh (near[v].v) vào tập T. Sau khi đỉnh v được bố xung vào U thì với các đỉnh còn lại w trong hàng ưu tiên, khoá của nó có thê giảm, cần phải cập nhật. Chúng ta có thể mô tả thuật toán Prim một cách cụ thê hơn như saụ
Prim(GŒ,T) T = Ø; //Khởi tạo tập cạnh T rỗng for (mỗi đỉnh ve V) { key[v| = c(s,v); — /⁄s là đỉnh bât kỳ near|v| = $; }
Khởi tạo hàng ưu tiên P chứa tất cả các đỉnh v # s - với khoá của v là key[v]; while (P không rông)
{ v = DeleteMin(P); v = DeleteMin(P); T = TU {(near[v].v)}; for ( mỗi w kề v và w e P) Iƒ( c(v,w) < key[w] ) { DecreaseKey (P,w,c(v,W)); near|W| = v; / /
Chúng ta có nhận xét rằng, dòng điều khiển của thuật toán Prim trên là giống hệt thuật toán Dijkstra, do đó ta có thể kết luận rằng, nếu sử dụng hàng ưu tiên trên được cài đặt bởi cây thứ tự bộ phận, thì thời gian chạy của thuật toán Prim cũng là O(JVIlogl|VỊ + |Ellog|VỊ). Nhưng G = (V,E) là đồ thị
vô hướng liên thông, nên |E| >= |VỊ - 1. Do đó, thời gian chạy của thuật toán Prim là O(|Ellog|VỊ).
18.62 — Thuật toán Kruskal
Thuật toán Kruskal cũng được thiết kế theo kỹ thuật tham ăn. Tập T các cạnh được xây dựng dân từng bước xuất phát từ T rỗng. Nhưng khác với thuật toán Prim, tại mỗi bước trong thuật toán Kruskal, cạnh (ụv) được chọn thêm vào T là cạnh ngăn nhất trong các cạnh còn lại và không tạo thành chu trình với các cạnh đã có trong T. Vấn đề đặt ra là, tại mỗi bước khi xét cạnh (ụv) ngắn nhất trong các cạnh còn lại, làm thế nào để biết được (ụv) có tạo thành chu trình với các cạnh đã có trong T hay không?
Chú ý rằng, một tập T các cạnh không tạo thành chu trình sẽ phân hoạch tập đỉnh V của đồ thị thành một họ các tập con không cắt nhau, mỗi tập con gồm các đỉnh được nối với nhau bởi các cạnh trong T và hai đỉnh năm trong hai tập con khác nhau thì không được nối với nhaụ Khi ta xét cạnh (ụv) ngắn nhất trong các cạnh còn lại, nếu cả hai đỉnh u và v cùng nằm trong một tập con. thì vì tất cả các đỉnh năm trong tập con này đã được nối với nhau, nên một chu trình sẽ được tạo ra khi ta thêm vào cạnh (u,v); còn nếu u, v năm trong hai tập con khác nhau, thì khi thêm (ụv) và T sẽ không có chu trình nào được tạo rạ Thuật toán Kruskal sử dụng CTDL họ các tập con không cắt nhau (xem chương 13) để bảo trì họ các tập con các đỉnh được phân hoạch bởi tập T. Khi xét cạnh (u,v), ta áp dụng phép Find(u), Find(v) để tìm tập con chứa u và tập con chứa v. Nếu Find(u) # Find(v) thì ta thêm (ụv) vào T, rồi áp dụng phép toán Union(ụv) để hợp nhất tập con chứa u và tập con chứa v thành một. Thuật toán Kruskal được mô tả như sau
Kruskal(G,1)
//Xây dựng cây bao trùm ngắn nhất T của đồ thị G = (V.E).
Ẳ
T =i;
Sắp xếp các cạnh (ụv)e E thành một danh sách L không giảm theo độ dài; Khởi tạo một họ tập con, mỗi tập con chứa một đỉnh ve V; k=0: /Biến đếm số cạnh được đưa vào T
do ƒ
Loại cạnh (u,v) ở đầu danh sách L (tức là cạnh ngắn nhất trong các cạnh còn lại): ¡ = Find(u); /⁄¡ là đại biểu của tập con chứa u
J= Find(v); If(1#])
Ẳ
T= TU({(u/v)}:
Union(ụv); /Lấy hợp của các tập con chứa u và v. k++;
; } }