Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 14 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
14
Dung lượng
546,27 KB
Nội dung
Buổi Mục tiêu - Biểu diễn đồ thị có trọng số - Cài đặt giải thuật Moore - Dijkstra tìm đường ngắn từ đỉnh đến đỉnh khác - Cài đặt giải thuật Bellman – Ford - Cài đặt giải thuật Floyd Yêu cầu: - Biểu diễn đồ thị - Các phép toán đồ thị Đồ thị có trọng số 11 42 m 95 m 12 10 100 m 140 m 50 m 37 m 180 m 40 m 60 m Xét đồ khu ĐHCT hình Giả sử ta muốn tìm đường ngắn từ giao điểm đến giao điểm Ta mơ hình hố đồ đồ thị với đỉnh giao điểm cung đường Ta thu đồ thị G = Rõ ràng để biết chiều dài đường ta cần phải biết chiều đường phải biểu diễn chúng đồ thị Vì thế, với cung đồ thị (ứng với đường) ta gán cho số Con số gọi chiều dài cung hay tổng quát trọng số cung Một số đồ thị cung gán trọng số gọi đồ thị có trọng số Biểu diễn đồ thị có trọng số Xét đồ thị G = , với cung e = (u, v) G gán trọng số l(e) Ta mở rộng phương pháp Ma trận kề (đỉnh – đỉnh) để biểu diễn đồ thị có trọng số - Ma trận có số hàng số cột với số đỉnh - Phần tử A[i][j] chứa trọng số (hay chiều dài) cung (i, j) - Nếu khơng có cung (i, j) phần tử A[i][j] chứa giá trị giá trị đặc biệt khác với trọng số cung, gọi giá trị NO_EDGE Chú ý: - Phương pháp sử dụng với đồ thị có hướng vô hướng - Không sử dụng với đồ thị có đa cung (vì có A[i][j] nên chứa giá trị) Ví dụ: #define MAXN 1000 #define NO_EDGE //hoac gia tri dac biet nao typedef struct { int n; int L[MAXN][MAXN]; } Graph; Khởi tạo đồ thị ta cho tất phần tử L NO_EDGE: khơng có cung void init_graph(Graph* G, int n) { G->n = n; int i, j; for (i = 1; i i - Đánh đấu xét i cách đặt mark[i] = - Xem xét cập nhật pi[j] p[j] đỉnh kề i chưa xét (mark[j] == 0) o Một đỉnh cập nhật đường (thông qua i) tốt đường cũ if (pi[i] + L[i][j] < pi[j]) { pi[j] = pi[i] + L[i][j] p[j] = i } Cài đặt giải thuật: #define INFINITY 9999999 int mark[MAXN]; int pi[MAXN]; int p[MAXN]; void Dijkstra(Graph* G, int s) { int i, j, it; for (i = 1; i n; i++) { pi[i] = INFINITY; mark[i] = 0; } pi[s] = 0; p[s] = -1; //trước đỉnh s khơng có đỉnh // lặp n n-1 lần for (it = 1; it < G->n; it++) { //1 Tìm i có mark[i] == va có pi[i] nhỏ int min_pi = INFINITY; for (j = 1; j n; j++) if (mark[j] == && pi[j] < min_pi) { min_pi = pi[j]; i = j; } //Đánh dấu i xét mark[i] = 1; //2 Cập nhật pi p đỉnh kề i (nếu thoả) for (j = 1; j n; j++) if (G->L[i][j] != NO_EDGE && mark[j] == 0) { if (pi[i] + G->L[i][j] < pi[j]) { pi[j] = pi[i] + G->L[i][j]; p[j] = i; } } } } Sau gọi hàm Dijkstra kết lưu mảng pi[] p[] Ta in kết , hình để kiểm tra: for (i = 1; i =0; i ) printf(“%d “, path[i]); Bài tập Viết chương trình đọc đồ thị có hướng từ tập tin, tìm đường ngắn từ đỉnh đến đỉnh lại In thơng tin pi[i] p[i] đỉnh hình Bài tập Viết chương trình đọc đồ thị vơ hướng từ tập tin, tìm đường ngắn từ đỉnh đến đỉnh lại In thơng tin pi[i] p[i] đỉnh hình Chú ý: đồ thị vô hướng thêm cung (u, v) vào đồ thị ta thêm cung (v, u) vào đồ thị Bài tập (ứng dụng) – Mê cung số (number maze) Cho mê cung số biểu diễn mảng chiều chứa số từ đến (xem hình bên dưới) Một robot đặt góc bên trái mê cung muốn đến góc bên phải mê cung Con robot lên, xuống, qua trái qua phải Chi phí để đến với số bên 3 9 Hãy tìm cách giúp robot đến góc phải cho tổng chi phí thấp Đường có chi phí thấp cho ví dụ 24 Dữ liệu đầu vào cho tập có định dạng sau: - Dòng dầu chứa số nguyên M N (M: số hàng, N: số cột) - M dòng mơ tả số mê cung Ví dụ lưu sau: 45 03129 73499 17553 23425 Dữ liệu đầu ra: in chi phí thấp để robot từ góc bên trái góc bên phải Ví dụ trên, cần in hình: 24 Gợi ý giải: - Mơ hình hố tốn đồ thị có hướng o Đỉnh o Cung hai ô cạnh o Trọng số cung (u, v) giá trị ô tương ứng với đỉnh v Cách - Đánh số ô: Giả sử ô mê cung đánh số từ (0, 0): góc trái đến (M-1, N-1): góc phải - 4 10 11 12 13 14 15 16 17 18 19 20 Ô (i,j) tương ứng với đỉnh (i*N + j) + Đỉnh u tương ứng với ô hàng (u-1)/N cột (u-1)%N Liệt kê đỉnh kề đỉnh: kề với ô xung quanh nên đỉnh có nhiều đỉnh kề tương ứng Ta dùng công thức biến đổi ô -> đỉnh đỉnh -> bên để tìm đỉnh kề đỉnh Giả sử ta muốn tìm đỉnh kề đỉnh u, ta làm sau: - Đổi u thành hàng i = (u – 1)/N cột j = (u - 1)%N - Tìm xung quanh (i, j) (i-1, j), (i+1, j), (i, j -1) (i, j + 1) - Đổi ô thành đỉnh (nếu ô nằm phạm vi (0,0) (M-1, N-1) Sử dụng khung chương trình bên để cặp nhật pi p đỉnh kề đỉnh u giải thuật Dijkstra int di[] = {-1, 1, 0, 0}; int dj[] = { 0, 0, -1, 1}; //Đổi đỉnh u thành ô (i, j) int i = (u – 1)/N; int j = (u – 1)%N; //Duyệt qua ô kề ô (i, j) for (k = 0; k < 4; k++) { ii = i + di[k]; jj = j + dj[k]; //Kiểm tra ô (ii,jj) có nằm mê cung khơng if (ii >= && ii < M && jj >= && jj < N) { v = ii*N + jj; //đổi ô (ii,jj) thành đỉnh v //v đỉnh kề đỉnh u … } Cách } – sử dụng số hàng côt ô làm số đỉnh - Không cần đổi ô thành đỉnh - Sử dụng mảng pi[i][j] (2 chiều) thay pi[u] (1 chiều) Giải thuật Bellman – Ford Tương tự giải thuật Dijkstra, cho phép tìm đường ngắn từ đỉnh đến đỉnh khác Ý tưởng: - Khởi tạo: giống Dijkstra o pi[i] = oo với i != s; o pi[s] = 0, p[s] = -1; - Lặp n – lần o Duyệt qua tất cung (u, v) cập nhật pi[v] p[v] thoả điều kiện if (pi[u] + L[u][v] < pi[v]) { pi[v] = pi[u] + L[u][v]; p[v] = u; } Về bản, giải thuật Bellman – Ford có nguyên lý giải thuật Dijkstra Điểm khác biệt lần lặp: giải thuật Dijkstra chọn đỉnh có pi[u] bé cập nhật đỉnh kề nó; giải thuật Bellman – Ford duyệt qua kết tất cung (u, v) cập nhật đỉnh v Vì để thuận tiện cho giải thuật Bellman – Ford ta phải biểu diễn đồ thị cho duyệt qua cung dễ dàng Cách đơn giản lưu danh sách cung đồ thị typedef struct { int u, v; // đỉnh đầu v, đỉnh cuối v int w; // trọng số w } Edge; typedef struct { int n, m; // n: đỉnh, m: cung Edge edges[1000]; // lưu cung đồ thị } Graph; Khởi tạo đồ thị: void init_graph(Graph* G, int n) { G->n = n; G->m = 0; } Thêm cung vào đồ thị: void add_edge(Graph* G, G->edges[G->m].u = G->edges[G->m].v = G->edges[G->m].w = G->m++; } int u, int v, int w) { u; v; w; Giải thuật Bellman – Ford: #define INFINITY 9999999 int pi[MAXN]; int p[MAXN]; void BellmanFord(Graph* G, int s) { int i, j, it; for (i = 1; i n; i++) { pi[i] = INFINITY; } pi[s] = 0; p[s] = -1; //trước đỉnh s khơng có đỉnh // lặp n n-1 lần for (it = 1; it < G->n; it++) { // Duyệt qua cung cập nhật (nếu thoả) for (k = 0; k < G->m; k++) { int u = G->edges[k].u; int v = G->edges[k].v; int w = G->edges[k].w; if (pi[u] + w < pi[v]) { pi[v] = pi[u] + w; p[v] = u; } } } } Kết giải thuật Bellman – Ford sử dụng giống giải thuật Dijkstra Phát chu trình âm: Giải thuật Bellman – Ford giải thuật Dijkstra chỗ chạy với đồ thị có trọng số âm Sau chạy xong giải thuật, ta phát chu trình âm cách duyệt qua cung lần tiếp tục cải tiến pi[v] có nghĩa có chu trình âm 10 // Duyệt qua cung lần for (k = 0; k < G->m; k++) { int u = G->edges[k].u; int v = G->edges[k].v; int w = G->edges[k].w; if (pi[u] + w < pi[v]) { // Có chu trình âm } } Bài tập Cài đặt giải thuật Bellman – Ford tìm đường ngắn từ đỉnh đến đỉnh lại đồ thị (trọng số âm) In giá trị pi[i] p[i] đỉnh hình In đường ngắn từ s đến t Hãy kiểm thử với đồ thị bên (s: đỉnh 1, t: đỉnh 8): Bài tập Áp dụng giải thuật Bellman – Ford kiểm tra xem đồ thị có chứa chu trình âm hay khơng In kết YES (nếu đồ thị có chu trình âm) NO (trường hợp ngược lại) Bài tập (nâng cao) Tương tự tập 5, thay in YES/NO in đỉnh chu trình âm 11 Giải thuật Floyd – Warshall Tìm đường ngắn tất cặp đỉnh (all pair shortest path) Ý tưởng: áp dụng quy hoạch động tìm đường ngắn đỉnh u, v thông qua đỉnh trung gian {1, 2, , k} Gọi pi(u, v, k) chiều dài đường ngắn từ u đến v thông qua đỉnh {1, 2, …, k}, ta có: - pi(u, v, 0) = trọng số cung (u,v): trực tiếp từ u đến v - pi(u, v, k) = min{pi(u, v, k-1), pi(u, k, k-1) + pi(k, v, k-1)} Đường ngắn từ u đến v là: pi(u, v, n) Biến hỗ trợ - pi[u][v]: chiều dài đường ngắn từ u đến v - next[u][v]: đỉnh đỉnh u đường ngắn từ u đến v Giải thuật: Khởi tạo: - pi[u][v] = oo (vô cùng), với u, v - pi[u][u] = 0, với u - pi[u][v] = L[u][v], với cung (u, v) đồ thị - next[u][v] = -1, với cặp u, v - next[u][v] = v với cung (u, v) đồ thị Lặp k = đến n - Với cặp đỉnh (u, v), cập nhật lại pi[u][v] thoả điều kiện if (pi[u][k] + pi[k][v] < pi[u][v]) { pi[u][v] = pi[u][k] + pi[k][v]; next[u][v] = next[u][k]; } Dựng lại đường từ u đến v dựa vào next[u][v] path = [u]; while (u != v) { u = next[u][v]; path = path + [u]; //Thêm u vào đường //Ta in u hình thay lưu vào đường path } 12 Cài đặt giải thuật: Sử dụng cách biểu diễn ma trận trọng số giải thuật Moore – Dijkstra #define INFINITY 9999999 int pi[MAXN][MAXN]; int next[MAXN][MAXN]; void Floyd_Warshall(Graph* G) { int u, v, k; for (u = 1; u n; u++) for (v = 1; v n; v++) { pi[u][v] = INFINITY; next[u][v] = -1; } for (u = 1; u n; u++) p[u][u] = 0; for (u = 1; u n; u++) for (v = 1; v n; v++) if (G->L[u][v] != NO_EDGE) { pi[u][v] = G->L[u][v]; next[u][v] = v; } for (k = 1; k n; k++) for (u = 1; u n; u++) for (v = 1; v n; v++) if (pi[u][k] + p[k][v] < pi[u][v]) { pi[u][k] = pi[u][k] + pi[k][v]; next[u][v] = next[u][k]; } } Bài tập Viết chương trình đọc đồ thị có trọng số từ tập tin Áp dụng giải thuật Floyd – Warshall tìm đường ngắn cặp đỉnh In chiều dài ngắn cặp đỉnh hình theo dạng: x -> y: chiều dài … Bài tập Viết chương trình đọc đồ thị có trọng số từ tập tin In đường ngắn đỉnh theo dạng: x -> y: x -> u1 -> u2 -> … -> y 13 Phát chu trình âm Sau chạy giải thuật Floyd – Warshall đường chéo ma trận chiều dài đường pi[u][u] < (với u bất kỳ) đồ thị cho chứa chu trình âm Khi khởi tạo, ta gán pi[u][u] = pi[u][u] cập nhật đường từ u -> … -> u có chiều dài âm Tức có chu trình âm ! int negative_cycle = 0; for (u = 1; u n; u++) if (pi[u][u] < 0) { //Đồ thị có chứa chu trình âm negative_cycle = 1; break; } Bài tập Viết chương trình đọc đồ thị có trọng số từ tập tin Áp dụng giải thuật Floyd – Warshall kiểm tra đồ thị có chưa chu trình âm khơng Nếu có in chu trình âm đó, khơng in “No negative cycle” 14 ... cho ví dụ 24 Dữ liệu đầu vào cho tập có định dạng sau: - Dòng dầu chứa số nguyên M N (M: số hàng, N: số cột) - M dòng mơ tả số mê cung Ví dụ lưu sau: 45 03129 73499 17553 23425 Dữ liệu đầu ra: in