Thuật Toán Prim Kruskal
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.
Bướ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=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.
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.d>a[j].d then
Begin
tam: a;
a:=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.chon= true) or not (v[a.d1) xor v[a.d2]) and (i<=m) do inc(i);
If (i<=m) then
Begin
a.chon:=true;
v[a.d1]:=true;
v[a.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.chon then
Write(fo, a.d1,′ ′, a.d2);
End
Else write(fo,′ Do thi khong lien thong′)