LI R RL IR
6. 2.3 Danh sách kề
6.6.2. Tìm cây bao trùm ngắn nhất
Bài toán tìm cây bao trùm nhỏ nhất là một trong những bài toán tối ƣu trên đồ thị có ứng dụng trong nhiều lĩnh vực khác nhau của thực tế. Bài toán đƣợc phát biểu nhƣ sau:
Cho G=(V, E) là đồ thị vô hƣớng liên thông với tập đỉnh V = {1, 2, . . ., n } và tập cạnh E gồm m cạnh. Mỗi cạnh e của đồ thị đƣợc gán với một số không âm c(e) đƣợc gọi là độ dài của nó. Giả sử H=(V, T) là một cây bao trùm của đồ thị G. Ta gọi độ dài c(H)
của cây bao trùm H là tổng độ dài các cạnh của nó:
T e e c H c( ) ( ). Bài toán đƣợc đặt ra là,
trong số các cây khung của đồ thị hãy tìm cây khung có độ dài nhỏ nhất của đồ thị.
Để minh họa cho những ứng dụng của bài toán này, chúng ta có thể tham khảo hai mô hình thực tế của bài toán.
Bài toán nối mạng máy tính. Một mạng máy tính gồm n máy tính đƣợc đánh số từ 1, 2, . . ., n. Biết chi phí nối máy i với máy j là c[i, j], i, j = 1, 2, . . ., n. Hãy tìm cách nối mạng sao cho chi phí là nhỏ nhất.
Bài toán xây dựng hệ thống cable. Giả sử ta muốn xây dựng một hệ thống cable điện thoại nối n điểm của một mạng viễn thông sao cho điểm bất kỳ nào trong mạng đều có đƣờng truyền tin tới các điểm khác. Biết chi phí xây dựng hệ thống cable từ điểm i đến điểm j là c[i,j]. Hãy tìm cách xây dựng hệ thống mạng cable sao cho chi phí là nhỏ nhất.
Để giải bài toán cây bao trùm nhỏ nhất, chúng ta có thể liệt kê toàn bộ cây bao trùm và chọn trong số đó một cây nhỏ nhất. Phƣơng án nhƣ vậy thực sự không khả thi vì số cây bao trùm của đồ thị là rất lớn cỡ nn-2, điều này không thể thực hiện đƣợc với đồ thị với số đỉnh cỡ chục.
Để tìm một cây bao trùm chúng ta có thể thực hiện theo các bƣớc nhƣ sau:
Bƣớc 1. Thiết lập tập cạnh của cây bao trùm là . Chọn cạnh e = (i, j) có độ dài nhỏ
nhất bổ sung vào T.
Bƣớc 2. Trong số các cạnh thuộc E \ T, tìm cạnh e = (i1, j1) có độ dài nhỏ nhất sao cho khi bổ sung cạnh đó vào T không tạo nên chu trình. Để thực hiện điều này, chúng ta phải chọn cạnh có độ dài nhỏ nhất sao cho hoặc i1 T và j1 T, hoặc j1 T và i1 T.
Bƣớc 3. Kiểm tra xem T đã đủ n-1 cạnh hay chƣa? Nếu T đủ n-1 cạnh thì nó chính là cây bao trùm ngắn nhất cần tìm. Nếu chƣa đủ n-1 cạnh thì thực hiện lại bƣớc 2.
2 20 4
33 8
1 18 16 9 6
17 14
3 4 5
Hình 6.21. Đồ thị vô hướng liên thông G=(V, E)
Bƣớc 1. Đặt T=. Chọn cạnh (3, 5) có độ dài nhỏ nhất bổ sung vào T.
Buớc 2. Sau ba lần lặp đầu tiên, ta lần lƣợt bổ sung vào các cạnh (4,5), (4, 6). Rõ ràng, nếu bổ sung vào cạnh (5, 6) sẽ tạo nên chu trình vì đỉnh 5, 6 đã có mặt trong T. Tình huống tƣơng tự cũng xảy ra đối với cạnh (3, 4) là cạnh tiếp theo của dãy. Tiếp đó, ta bổ sung hai cạnh (1, 3), (2, 3) vào T.
Buớc 3. Tập cạnh trong T đã đủ n-1 cạnh: T={ (3, 5 ), (4,6), (4,5), (1,3), (2,3) } chính là cây bao trùm ngắn nhất.
Chƣơng trình tìm cây bao trùm ngắn nhất đƣợc thể hiện nhƣ sau: #include <stdio.h> #include <conio.h> #include <stdlib.h> #include <math.h> #include <dos.h> #define MAX 50 #define TRUE 1 #define FALSE 0
int E1[MAX], E2[MAX], D[MAX], EB[MAX], V[MAX];/* E1 : Lƣu trữ tập
đỉnh đầu của các cạnh;
E2 : Lƣu trữ tập đỉnh cuối của các cạnh;
D : Độ dài các cạnh;
EB : Tập cạnh cây bao trùm ;
V : Tập đỉnh của đồ thị cũng là tập đỉnh của cây bao trùm;
*/
int i, k, n, m, sc, min, dai; FILE *fp;
void Init(void){
fp=fopen("BAOTRUM.IN","r"); if(fp==NULL){
printf("\n Khong co file Input"); getch(); return;
}
printf("\n So dinh do thi:%d",n); printf("\n So canh do thi:%d", m); printf("\n Danh sach canh:"); for (i=1; i<=m; i++){
fscanf(fp,"%d%d%d", &E1[i],&E2[i], &D[i]); printf("\n%4d%4d%4d",E1[i], E2[i], D[i]); }
fclose(fp);
for(i=1; i<=m; i++) EB[i]=FALSE; for(i=1; i<=n; i++) V[i]= FALSE; }
void STREE_SHORTEST(void){
/* Giai đoạn 1 của thuật toán là tìm cạnh k có độ dài nhỏ nhất*/ min = D[1]; k=1;
for (i=2; i<=m; i++) { if(D[i]<min){
min=D[i]; k=i; }
}
/* Kết nạp cạnh k vào cây bao trùm*/
EB[k]=TRUE; V[E1[k]]=TRUE; V[E2[k]]=TRUE;sc=1; do {
min=32000;
for (i=1; i<=m; i++){
if (EB[i]==FALSE && (
( (V[E1[i]]) && (V[E2[i]]==FALSE))||
( ( V[E1[i]]==FALSE ) && (V[E2[i]]==TRUE ) ) ) && (D[i]<min) ){
min=D[i]; k=i; }
}
/* Tìm k là cạnh nhỏ nhất thỏa mãn điều kiện nếu kết nạp cạnh vào cây sẽ không tạo nên chu trình*/
EB[k]=TRUE;V[E1[k]]=TRUE; V[E2[k]]=TRUE;sc=sc+1; }while(sc!=(n-1));
}
void Result(void){
printf("\n Cay bao trum:"); dai=0;
for (i=1; i<=m; i++){ if(EB[i]){
printf("\n Canh %4d %4d dai %4d", E1[i], E2[i], D[i]); dai=dai+D[i];
} }
printf("\n Do dai cay bao trum:%d", dai); } void main(void){ Init(); STREE_SHORTEST(); Result(); getch(); }
Chúng ta sẽ xét một vài thuật toán hiệu quả hơn. Đó là thuật toán Kruskal và thuật toán Prim.