Thuật toán Prim - Kruscal
Trang 1Thuật toán Prim - Kruscal
Đào Đức Minh
Bài toán: Cho một đồ thị vô hướng Hãy tìm cây bao trùm ngắn nhất (Tức là tổng các
trọng số các cạnh của cây là nhỏ nhất)
Giới hạn:
INPUT:
Dòng đầu: Gồm 2 số n,d: (n,d thuộc [0;100]), n là số đỉnh, d: Số cạnh
D dòng tiếp theo: Mỗi dòng gồm các số i,j,s
+ Cho biết trọng số cạnh i,j là s (i,j là số hiệu đỉnh)
OUTPUT:
- Dòng đầu: Gồm 1 số m duy nhất cho biết số cạnh của cây (luôn bằng (n-1))
- m (hay (n-1)) dòng tiếp theo: Mỗi dòng gồm các số a,b,s: Cho biết đỉnh a nối với đỉnh b
có trọng số cạnh ab là s
(n-1) dòng tiếp theo này cho biết các cạnh của cây
- Dòng cuối: T cho biết tổng trọng số của cây bao trùm ngắn nhất
- Nếu không liên thông thì xuất ″đồ thị không liên thông″
*) Giải thuật:
Có lẽ thuật toán Prim và Kruscal đã quen thuộc để giải loại bài toán này (Đã từng được giới thiệu trên báo THNT) , Nhưng mỗi thuật toán tôi lại cảm thấy không được tối ưu cho lắm vì các bước cần giải quyết khá phức tạp
*) Thuật toán Prim: Tư tưởng chủ đạo là chọn cạnh, mỗi lần chọn cạnh lại phải kiểm tra
có tạo chu trình hay không, nếu cạnh ấy có tạo chu trình thì loại bỏ cạnh ấy
Thuật toán Kruscal: Tư tưởng chủ đạo là chọn đỉnh, phải chọn đỉnh không ở trong tập
đỉnh đã chọn, có cung nối đỉnh đó với đỉnh đã chọn và có trọng số là nhỏ nhất
Các bước trong thuật toán trên khá phức tạp, tốn kém thời gian, khi dữ liệu quá lớn thì có
lẽ đó là chưa là thuật toán tốt Em đã nghĩ ra thuật toán kết hợp tinh hoa của hai thuật toán trên trở thành ″Thuật toán Prim − Kruscal″
Các bước của thuật toán:
Bước 1 Sắp xếp các cạnh từ nhỏ đến lớn theo trọng số (Dùng kiểu record), Chọn cạnh
đầu tiên
Bước 2 Tìm cạnh tiếp theo: cạnh được chọn phải thoả: 1 đỉnh ở trong tập hợp đỉnh đã
chọn và một đỉnh ngoài tập hợp đỉnh đã chọn và cạnh đó chưa được chọn
Bước 3: Trở lại đầu dãy cạnh đã sắp xếp
Trở lại bước 2 và chỉ thoát khi chọn đủ (n-1) cạnh hoặc chưa chọn đủ (n-1) cạnh nhưng không thể chọn thêm cạnh nữa (trường hợp này đồ thị không liên thông)
*) Ví dụ minh hoạ: Gọi T là tập các đỉnh đã chọn, C là tâp các cạnh đã chọn
Trang 2Bước 1 Sắp xếp các cạnh theo chiều tăng của trọng số:
(1,2) (4,3) (3,5) (4,5) (1,4) (1,5) (2,5)
Trọng số 1 1 2 2 3 4 5
Chọn cạnh đầu tiên: (1,2) Khi đó, cạnh (1,2) đã chọn, đỉnh1,2 đã chọn C= { (1,2)}, T= {1,2}
Bước 2 Chọn cạnh tiếp theo: Duyệt từ đầu dãy: (1,2) đã chọn ; (4,3) chưa chọn nhưng
không thỏa: có 1 đỉnh trong, 1 đỉnh ngoài tập T; (3,5) không chọn được với lý do tương tự; (4,5) không chọn, (1,4) : cạnh này chưa chọn và có một đỉnh trong tập T, 1 đỉnh ngoài tập T nên chọn Khi đó:
C={(1,2), (1,4)}
T={1,2,4}
Bước 3 - Kiểm tra đủ (n-1) cạnh trong C chưa hoặc không còn có thể chọn cạnh tiếp hay
không, khi đó thoát
Trở lại đầu dãy trỏ lại bước 2
Bước 2: Duyệt lại từ đầu:
(1,2): đã có trong tập C nên không chọn, (4,3): chưa chọn, 1 đỉnh trong, 1 đỉnh ngoài T nên chọn Khi đó:
C={(1,2), (4,3), (1,4)}
T={1,2,4,3}
Cứ tiếp tục đến khi kết thúc (kiểm tra kết thúc ở bước 3)
Chương trình:
Type canh: record
d1,d2,d: word;
chon: Boolean;
end;
var a: array[1 10000] of canh;
v: array[1 10000] of boolean;
+) canh: d1,d2: 2 đỉnh của các cạnh, d: trọng số
+) mảng cạnh: a
+) v: v[i]=true khi và chỉ khi i thuộc T
+) cạnh có thành phần chọn: cho biết chọn hay không canh[i]
Với khai báo trên:
{Bước 1: Sắp xếp}
for i:=1 to m-1 do
for j:=1 to m do
If a[i].d>a[j].d then
Trang 3Begin
tam: a[i];
a[i]:=a[j];
a[j]:=tam;
end;
{Chọn cạnh đầu tiên}
a[1].chon:= true; v[[a[1].d1]:=true; v[a[1].d2]:=true;
{Bước 2, bước 3}
Repeat
I:=1; inc(spt);
While (a[i].chon= true) or not (v[a[i].d1) xor v[a[i].d2]) and (i<=m) do inc(i);
If (i<=m) then
Begin
a[i].chon:=true;
v[a[i].d1]:=true;
v[a[i].d2]:=true;
end;
Until (spt=n-1) or (i>m);
{Xuất kết quả}
If (i<=m) then
Begin
For i:=1 to m do
If a[i].chon then
Write(fo, a[i].d1,′ ′, a[i].d2);
End
Else write(fo,′ Do thi khong lien thong′);