BÀI TẬP LỚN MÔN: CẤU TRÚC DỮ LIỆU VÀ GIẢI THUẬT Đề tài: Cài đặt thuật toán Prim tìm khung nhỏ đồ thị vô hướng I Giới thiệu chung thuật toán Prim ứng dụng Bài toán khung nhỏ đồ thị số toán tối ưu đồ thị tìm ứng dụng nhiều lĩnh vực khác đời sống Trước hết phát biểu nội dung toán: Cho G=(V,E) đồ thị vô hướng liên thông với tập đỉnh V={1,2,…,n} tập cạnh E gồm m cạnh Mỗi cạnh E đồ thị G gán với số không âm c(e), gọi độ dài Giả sử H=(V,T) khung đồ thị G Ta gọi độ dài c(H) khung H tổng độ dài cạnh nó: C(H)=∑e c(e) Bài toán đặt tất khung đò thị G tìm khung với độ dài nhỏ Cây khung gọi thoán khung nhỏ Đối với toán khung nhỏ có thuật toán hiệu để giải chúng Một số thuật toán Prim Thuật toán Prim gọi thuật toán lân cận gần Trong phương pháp đỉnh tùy ý đồ thị, ta nối s với đỉnh lân cận gần nhất, chẳng hạn đỉnh y Nghĩa số cạnh kề đỉnh s, cạnh (s,y) có độ dài nhỏ Tiếp theo số cạnh kề với hai đỉnh s đỉnh y ta tìm cạnh có độ dài nhỏ nhất, cạnh dẫn đến đỉnh thứ ba z, vf ta thu phận gồm đỉnh cạnh Quá trình tiếp tục ta thu gồm n đỉnh n-1 cạnh khung nhỏ cần tìm II Các bước thuật toán Prim Bước 1: Chọn đỉnh để bắt đầu( đưa đỉnh vào tập đỉnh VH), VH={s} Bước 2: Lặp lại công việc sau n-1 bước với n số đỉnh đồ thị {|V|=n} - Chọn đỉnh số đỉnh chưa chọn cho có cạnh ngắn tới đỉnh VH Kết nạp đỉnh vào VH Mô tả thuật toán: - - Tập VH( MST) ={}; Ở bước khởi tạo chọn đỉnh đưa vào VH, giả sử đỉnh s: VH={s}; Xây dựng danh sách( theo thứ tự tang dần) đỉnh với tiêu chí xếp theo khoảng cách từ đỉnh tới đỉnh VH, gọi tập Q Dùng mảng đánh dấu đỉnh chọn vào MST Thực thao tác sau n-1 bước: • Loại bỏ đỉnh v khỏi Q, v đỉnh có khoảng cách nhỏ với VH • Nếu V!=s kết nạp v vào VH • Xét đỉnh kề với v chưa kết nạp vào VH( đỉnh u): khoảng cách từ u tới VH lớn độ dài cung (v,u) cập nhật lại khoảng cách đó: d[u]=a[v][u] Thuật toán dừng sau n-1 cạnh kết nạp vào MST Một mảng preview để đánh dấu đỉnh trước đỉnh sử dụng kết nạp đỉnh v vào MST( thêm cạnh preview[v],v) vào cây) cập nhật thay đổi nhãn u preview[u]=v Ví dụ: Cho đồ thị vô hướng G sau: ST VH Q MST P T A A,D A,D,F A,D,F,B (A,0) (B,∞) (C,∞) (D,∞) (E,∞) (F,∞) (G,∞) (B,7) (C,∞) (D,5) (E,∞) (F,∞) (G,∞) (B,7) (C,∞) (E,15) (F,6) (G,∞) (B,7) (C,∞) (E,8) (G,11) (C,8) (E,7) (G,11) A,D,F,B,E (C,5) (G,9) A,D,F,B,E, C (G,9) A,D,F,B,E, C -1,-1,-1,-1,-1,1,-1 (A,D) (A,D)(D,F) (A,D)(D,F) (A,B) (A,D)(D,F) (A,B) (B,E) (A,D)(D,F) (A,B) (B,E) (E,C) (A,D)(D,F) (A,B) (B,E) (E,C)(E,G) -1,A,-1,A,-1,1,-1 -1,A,1,A,D,D,-1 -1,A,1,A,F,D,F -1, A,B,A,B,D,F -1, A,E,A,B,D,E -1, A,E,A,B,D,E Cây MST đò thị gồm cạnh (A,D) (D,F) (A,B) (B,E) (E,C) (E,G) có tổng trọng số 39 III Cài đặt thuật toán Prim #include #include #define nMax 100 #define INF 100000 #define true #define false typedef struct{ int u;//dinh dau int v;//dinh cuoi int do_dai; }Canh; int G[nMax][nMax]; //ma tran so int so_dinh; int so_canh = 0; int d[nMax]; int da_ket_nap[nMax]; int W = 0;//do rong cua cay khung int lien_thong = 1; Canh T[nMax]; void input(){ FILE *f = fopen("input.txt","r"); fscanf(f,"%d\n",&so_dinh); for(int i = 0; i < so_dinh; i++){ for(int j = 0; j < so_dinh; j++){ fscanf(f, "%d ", &G[i][j]); if(G[i][j] == && i != j) G[i][j] = INF; } fscanf(f,"\n"); } fclose(f); } void print_matrix(int a[][nMax], int n){ for(int i = 0; i < n; i++){ for(int j = 0; j < n; j++) { if(G[i][j] == INF) printf("INF "); else printf("%-3d ",G[i][j]); } printf("\n"); } } void prim(){ //khoi tao cay co dinh so d[0] = 0; da_ket_nap[0] = true; for(int i = 1; i < so_dinh; i++) d[i] = INF; W = 0; //ket nap cac dinh khac while(so_canh < so_dinh-1){ Canh c_min; int d_min; for(int i = 0; i < so_dinh; i++){ if(da_ket_nap[i] == false){ d_min = INF; for(int j = 0; j < so_dinh; j++){ if(da_ket_nap[j] == true){ if(G[i][j] < d_min){ c_min.u = i; c_min.v = j; c_min.do_dai = G[i][j]; d_min = G[i][j]; } } } if(d_min == INF){ lien_thong = false; return; } else{ break; } } } T[so_canh] = c_min; W += c_min.do_dai; da_ket_nap[c_min.u] = true; so_canh++; } } int main(){ input(); printf("Ma tran so: \n"); print_matrix(G, so_dinh); prim(); if(lien_thong == true){ printf("Do rong cua cay khung nho nhat: W = %d.\n",W); printf("Danh sach canh cua cay khung: \n"); for(int i = 0; i < so_canh; i++) printf("%d - %d (%d)\n",T[i].u, T[i].v, T[i].do_dai); } else{ printf("Do thi khong lien thong!.\n"); printf("Cac dinh chua dc ket nap: "); for(int i = 0; i < so_dinh; i++) if(da_ket_nap[i] == false) printf("%d ",i); } return 0; } Dữ liệu lấy từ file: Kết chạy chương trình: IV Đánh giá thuật toán Cấu trúc liệu tìm cạnh có trọng số nhỏ Tìm kiếm ma trận kề Đống nhị phân danh sách kề Đống Fibonacci danh sách kề Một cách lập trình đơn giản sử dụng ma trận kề tìm kiếm toàn mảng để tìm cạnh có trọng số nhỏ có thời gian chạy O(V2) Bằng cách sử dụng cấu trúc liệu đống nhị phân danh sách kề, giảm thời gian chạy xuống O(E log V) Bằng cách sử dụng cấu trúc liệu đống Fibonacci phức tạp hơn, giảm thời gian chạy xuống O(E+ V log V), nhanh thuật toán trước đồ thị có số cạnh E=ω(V) V Chứng minh tính đắn Đặt G đồ thị có trọng số liên thông Trong bước, thuật toán Prim chọn cạnh nối đồ thị với đỉnh không thuộc đồ thị Vì G liên thông nên tồn đường từ đồ thị tới tất đỉnh lại Kết T thuật toán Prim cây, cạnh đỉnh thêm vào T liên thông cạnh thêm không tạo thành chu trình với cạnh cũ Đặt T1 bao trùm nhỏ G Nếu T1=T T bao trùm nhỏ Nếu không, đặt e cạnh thêm vào T mà không thuộc T1, V tập hợp đỉnh thuộc T trước thêm e Một đầu e thuộc V đầu không thuộc V Vì T1 bao trùm G, nên tồn đường T1 nối hai đầu e Trên đường đó, định tồn cạnh f nối đỉnh V với đỉnh V Trong bước lặp e thêm vào Y, thuật toán chọn f thay e trọng số nhỏ e Vì f không chọn nên w(f) >= w(e) Đặt T2 đồ thị thu cách xóa f thêm e vào T1 Dễ thấyT2 liên thông, có số cạnh T1, tổng trọng số cạnh không trọng số T1, nên bao trùm nhỏ G chứa e tất cạnh thuật toán chọn trước Lặp lại lập luận nhiều lần, cuối ta thu bao trùm nhỏ G giống hệt T Vì T bao trùm nhỏ