Cấu trúc dữ liệu C++
Chương 13 – Đồ thị Chương 13 – ĐỒ THỊ Chương trình bày cấu trúc toán học quan trọng gọi đồ thị Đồ thị thường ứng dụng nhiều lónh vực: điều tra xã hội, hóa học, địa lý, kỹ thuật điện,… Chúng ta tìm hiểu phương pháp biểu điễn đồ thị cấu trúc liệu xây dựng số giải thuật tiêu biểu liên quan đến đồ thị 13.1 Nền tảng toán học 13.1.1 Các định nghóa ví dụ Một đồ thị (graph) G gồm tập V chứa đỉnh đồ thị, tập E chứa cặp đỉnh khác từ V Các cặp đỉnh gọi cạnh G Nếu e = (ν, µ) cạnh có hai đỉnh ν µ, gọi ν µ nằm e, e nối với ν µ Nếu cặp đỉnh thứ tự, G gọi đồ thị vô hướng (undirected graph), ngược lại, G đượïc gọi đồ thị có hướng (directed graph) Thông thường đồ thị có hướng gọi tắt digraph, từ graph thường mang nghóa đồ thị vô hướng Cách tự nhiên để vẽ đồ thị biểu diễn đỉnh điểm vòng tròn, cạnh đường thẳng cung nối đỉnh Đối với đồ thị có hướng đường thẳng hay cung cần có mũi tên hướng Hình 13.1 minh họa số ví dụ đồ thị Đồ thị thứ hình 13.1 có thành phố đỉnh, tuyến bay cạnh Trong đồ thị thứ hai, nguyên tử hydro carbon đỉnh, liên kết hóa học cạnh Hình thứ ba đồ thị có hướng cho biết khả truyền nhận liệu mạng, nút mạng (A, B, …, F) đỉnh đường nối nút có hướng Đôi cách chọn tập đỉnh tập cạnh cho đồ thị phụ thuộc vào giải thuật mà dùng để giải toán, chẳng hạn toán liên quan đến quy trình công việc, toán xếp thời khóa biểu,… Đồ thị sử dụng để mô hình hóa nhiều dạng trình cấu trúc khác Đồ thị biểu diễn mạng giao thông thành phố, thành phần mạch in điện tử đường nối chúng, cấu trúc phân tử gồm nguyên tử liên kết hóa học Những người dân thành phố biểu diễn đỉnh đồ thị mà cạnh mối quan hệ họ Nhân viên công ty biểu diễn đồ thị có hướng mà cạnh có hướng cho biết mối quan hệ họ với người quản lý Những người có mối quan hệ “cùng làm việc” biểu diễn cạnh không hướng đồ thị vô hướng Giáo trình Cấu trúc liệu Giải thuật 339 Chương 13 – Đồ thị Hình 13.1 – Các ví dụ đồ thị 13.1.2 Đồ thị vô hướng Một vài dạng đồ thị vô hướng minh họa hình 13.2 Hai đỉnh đồ thị vô hướng gọi kề (adjacent) tồn cạnh nối từ đỉnh đến đỉnh Trong đồ thị vô hướng hình 13.2 a, đỉnh kề nhau, đỉnh kề nhau, đỉnh đỉnh không kề Một đường (path) dãy đỉnh khác nhau, đỉnh kề với đỉnh Hình (b) cho thấy đường Một chu trình (cycle) đường chứa ba đỉnh cho đỉnh cuối kề với đỉnh Hình (c) chu trình Một đồ thị gọi liên thông (connected) có đường từ đỉnh đến đỉnh khác Hình (a), (b), (c) đồ thị liên thông Hình (d) đồ thị liên thông Nếu đồ thị không liên thông, xem tập lớn đỉnh liên thông thành phần liên thông Ví dụ, đồ thị không liên thông hình (d) có hai thành phần liên thông: thành phần chứa đỉnh 1,2 4; thành phần có đỉnh Hình 13.2 – Các dạng đồ thị vô hướng Giáo trình Cấu trúc liệu Giải thuật 340 Chương 13 – Đồ thị Phần (e) đồ thị liên thông chu trình Chúng ta nhận thấy đồ thị cuối thực cây, dùng đặc tính để định nghóa: Một tự (free tree) định nghóa đồ thị vô hướng liên thông chu trình 13.1.3 Đồ thị có hướng Đối với đồ thị có hướng, có định nghóa tương tự Chúng ta yêu cầu cạnh đường chu trình có hướng, việc lần theo đường chu trình có nghóa phải di chuyển theo hướng mũi tên Những đường (hay chu trình) gọi đường có hướng (hay chu trình có hướng) Một đồ thị có hướng gọi liên thông mạnh (strongly connected) có đường có hướng từ đỉnh đến đỉnh khác Trong đồ thị có hướng không liên thông mạnh, bỏ qua chiều cạnh mà có đồ thị vô hướng liên thông đồ thị có hướng ban đầu gọi đồ thị liên thông yếu (weakly connected) Hình 13.3 minh họa chu trình có hướng, đồ thị có hướng liên thông mạnh đồ thị có hướng liên thông yếu Hình 13.3 – Các ví dụ đồ thị có hướng Các đồ thị có hướng phần (b) (c) hình 13.3 có cặp đỉnh có cạnh có hướng theo hai chiều chúng Các cạnh có hướng cặp có thứ tự cặp có thứ tự (ν, µ) (µ,ν) khác ν ≠ µ Trong đồ thị vô hướng, có nhiều cạnh nối hai đỉnh khác Tương tự, đỉnh cạnh theo định nghóa phải khác nhau, có cạnh nối đỉnh với Tuy nhiên, có trường hợp mở rộng định nghóa, người ta cho phép nhiều cạnh nối cặp đỉnh, cạnh nối đỉnh với 13.2 Biểu diễn máy tính Nếu chuẩn bị viết chương trình để giải toán có liên quan đến đồ thị, trước hết phải tìm cách để biểu diễn cấu trúc toán học đồ thị dạng cấu trúc liệu Có nhiều phương pháp Giáo trình Cấu trúc liệu Giải thuật 341 Chương 13 – Đồ thị dùng phổ biến, chúng khác việc lựa chọn kiểu liệu trừu tượng để biểu diễn đồ thị, nhiều cách thực khác cho kiểu liệu trừu tượng Nói cách khác, định nghóa toán học, đồ thị, sau tìm hiểu cách mô tả kiểu liệu trừu tượng (tập hợp, bảng, hay danh sách dùng được), cuối lựa chọn cách thực cho kiểu liệu trừu tượng mà chọn 13.2.1 Biểu diễn tập hợp Đồ thị định nghóa tập hợp, cách tự nhiên dùng tập hợp để xác định cách biểu diễn liệu Trước tiên, có tập đỉnh, thứ hai, có cạnh tập cặp đỉnh Thay thử biểu diễn tập cặp đỉnh cách trực tiếp, chia thành nhiều phần nhỏ cách xem xét tập cạnh liên quan đến đỉnh riêng rẽ Nói cách khác, biết tất cạnh đồ thị cách nắm giữ tập Eν cạnh có chứa ν đỉnh ν đồ thị, hoặc, cách tương đương, tập Aν gồm tất đỉnh kề với ν Thật vậy, dùng ý tưởng để đưa định nghóa tương đương cho đồ thị: Định nghóa: Một đồ thị có hướng G bao gồm tập V, gọi đỉnh G, và, ν ∈ V, có tập Aν , gọi tập đỉnh kề ν Từ tập Aν tái tạo lại cạnh cặp có thứ tự theo quy tắc sau: cặp (ν, w) cạnh w∈ Aν Xử lý cho tập đỉnh dễ tập cạnh Ngoài ra, định nghóa thích hợp với đồ thị có hướng đồ thị vô hướng Một đồ thị vô hướng thỏa tính chất đối xứng sau: w∈ Aν kéo theo ν∈ Aw với ν, w∈V Tính chất phát biểu lại sau: Một cạnh hướng ν w xem hai cạnh có hướng, từ ν đến w từ w đến ν 13.2.1.1 Hiện thực tập hợp Có nhiều cách để thực tập đỉnh cấu trúc liệu giải thuật Cách thứ biểu diễn tập đỉnh danh sách phần tử nó, tìm hiểu phương pháp sau Cách thứ hai, thường gọi chuỗi bit (bit string), lưu trị Boolean cho phần tử tập hợp để có hay tập hợp Để đơn giản, xem phần tử có tập hợp đánh số từ đến max_set-1, với max_set số phần tử tối đa cho phép Điều thực cách dễ dàng cách sử dụng thư viện chuẩn (Standard Template Library- STL) Giáo trình Cấu trúc liệu Giải thuật 342 Chương 13 – Đồ thị std::bitset, lớp có sử dụng template cho kích thước tập hợp nhö sau: template struct Set { bool is_element[max_set]; }; Đây cách thực đơn giản khái niệm tập hợp Sinh viên thấy ngăn cản đặc tả thực CTDL tập hợp với phương thức hội, giao, hiệu, xét thành viên nó,…, cách hoàn chỉnh cần sử dụng tập hợp toán lớn Giờ đặc tả cách biểu diễn thứ cho đồ thị chúng ta: // Tương öùng hình 13.4-b template class Digraph { int count; // Số đỉnh đồ thị, nhiều max_size Set neighbors[max_size]; }; Trong cách thực này, đỉnh đặt tên số nguyên từ đến count-1 Nếu ν số nguyên phần tử neighbors[ν] mảng tập đỉnh kề với đỉnh ν 13.2.1.2 Bảng kề Trong cách thực đây, cấu trúc Set thực mảng phần tử kiểu bool Mỗi phần tử đỉnh tương ứng có thành phần tập hợp hay không Nếu thay tập đỉnh kề mảng, thấy mảng neighbors định nghóa lớp Graph biến đổi thành mảng mảng (mảng hai chiều) sau đây, gọi bảng kề (adjacency table): // Tương ứng hình 13.4-c template class Digraph { int count; // Số đỉnh đồ thị, nhiều max_size bool adjacency[max_size][max_size]; }; Giáo trình Cấu trúc liệu Giải thuật 343 Chương 13 – Đồ thị Bảng kề chứa thông tin cách tự nhiên sau: adjacency[v][w] true đỉnh v đỉnh kề w Nếu đồ thị có hướng, adjacency[v][w] cho biết cạnh từ v đến w có đồ thị hay không Nếu đồ thị vô hướng, bảng kề phải đối xứng, nghóa adjacency[v][w]= adjacency[v][w] với v w Biểu diễn đồ thị tập đỉnh kề bảng kề minh họa hình 13.4 (a) (b) Hình 13.4 – Tập đỉnh kề bảng kề (c) 13.2.2 Danh sách kề Một cách khác để biểu diễn tập hợp dùng danh sách phần tử Chúng ta có danh sách đỉnh, và, đỉnh, có danh sách đỉnh kề Chúng ta xem xét cách thực cho đồ thị danh sách liên tục danh sách liên kết đơn Tuy nhiên, nhiều ứng dụng, người ta thường sử dụng thực khác danh sách phức tạp nhị phân tìm kiếm, nhiều nhánh tìm kiếm, heap Lưu ý rằng, cách đặt tên đỉnh theo số cách thực trước đây, có cách thực cho tập đỉnh danh sách liên tục 13.2.2.1 Hiện thực dựa sở danh sách Chúng ta có thực đồ thị dựa sở danh sách cách thay tập hợp đỉnh kề trước danh sách Hiện thực sử dụng danh sách liên tục danh sách liên kết Phần (b) (c) hình 13.5 minh họa hai cách thực // Tổng quát cho danh sách liên tục lẫn liên kết (hình 13.5-b c) typedef int Vertex; template class Digraph { int count; // Số đỉnh đồ thị, nhiều max_size List neighbors[max_size]; }; Giáo trình Cấu trúc liệu Giải thuật 344 Chương 13 – Đồ thị 13.2.2.2 Hiện thực liên kết Bằng cách sử dụng đối tượng liên kết cho đỉnh cho danh sách kề, đồ thị có tính linh hoạt cao Hiện thực minh họa hình 13.5-a có định nghóa sau: Hình 13.5 – Hiện thực đồ thị danh sách class Edge; class Vertex { Edge *first_edge; // Vertex *next_vertex; // }; class Edge { Vertex *end_point; Edge *next_edge; // // Chỉ đến phần tử đầu DSLK đỉnh kề Chỉ đến phần tử kế DSLK đỉnh có đồ thị Chỉ đến đỉnh kề với đỉnh mà danh sách thuộc Chỉ đến phần tử biểu diễn đỉnh kề danh sách đỉnh kề với đỉnh mà danh sách thuộc }; Giáo trình Cấu trúc liệu Giải thuật 345 Chương 13 – Đồ thị class Digraph { Vertex *first_vertex;// }; Chỉ đến phần tử danh sách đỉnh đồ thị 13.2.3 Các thông tin khác đồ thị Nhiều ứng dụng đồ thị cần thông tin đỉnh kề đỉnh mà cần thêm số thông tin khác liên quan đến đỉnh cạnh Trong thực liên kết, thông tin lưu thuộc tính bổ sung bên ghi tương ứng, thực liên tục, chúng lưu mảng phần tử bên ghi Lấy ví dụ trường hợp mạng máy tính, định nghóa đồ thị cạnh có thêm thông tin tải trọng đường truyền từ máy qua máy khác Đối với nhiều giải thuật mạng, cách biểu diễn tốt dùng bảng kề, phần tử chứa tải trọng thay trị kiểu bool Chúng ta quay lại vấn đề sau chương 13.3 Duyệt đồ thị 13.3.1 Các phương pháp Trong nhiều toán, mong muốn khảo sát đỉnh đồ thị theo thứ tự Tựa nhị phân phát triển vài phương pháp duyệt qua phần tử cách có hệ thống Khi duyệt cây, thường nút gốc Trong đồ thị, thường đỉnh đỉnh đặc biệt, nên việc duyệt qua đồ thị đỉnh Tuy có nhiều thứ tự khác để duyệt qua đỉnh đồ thị, có hai phương pháp xem đặc biệt quan trọng Phương pháp duyệt theo chiều sâu (depth-first traversal) đồ thị gần giống với phép duyệt preorder cho có thứ tự Giả sử phép duyệt vừa duyệt xong đỉnh ν, gọi w1, w2, ,wk đỉnh kề với ν, w1 đỉnh duyệt kế tiếp, đỉnh w2, ,wk nằm đợi Sau duyệt qua đỉnh w1 duyệt qua tất đỉnh kề với w1, trước quay lại với w2, ,wk Phương pháp duyệt theo chiều rộng (breadth-first traversal) đồ thị gần giống với phép duyệt theo mức (level by level) cho có thứ tự Nếu phép duyệt vừa duyệt xong đỉnh ν, tất đỉnh kề với ν duyệt tiếp sau đó, đỉnh kề với đỉnh đặt vào danh sách chờ, chúng duyệt tới sau tất đỉnh kề với ν duyệt xong Giáo trình Cấu trúc liệu Giải thuật 346 Chương 13 – Đồ thị Hình 13.6 minh họa hai phương pháp duyệt trên, số đỉnh biểu diễn thứ tự mà chúng duyệt đến Hình 13.6 - Duyệt đồ thị 13.3.2 Giải thuật duyệt theo chiều sâu Phương pháp duyệt theo chiều sâu thường xây dựng giải thuật đệ quy Các công việc cần làm gặp đỉnh ν là: visit(v); for (mỗi đỉnh w kề với đỉnh v) traverse(w); Tuy nhiên, phép duyệt đồ thị, có hai điểm khó khăn mà phép duyệt Thứ nhất, đồ thị chứa chu trình, giải thuật gặp lại đỉnh lần thứ hai Để ngăn chặn đệ quy vô tận, dùng mảng phần tử kiểu bool visited, visited[v] true v vừa duyệt xong, xét trị visited[w] trước xử lý cho w, trị true w không cần xử lý Điều khó khăn thứ hai là, đồ thị không liên thông, giải thuật duyệt không đạt đến tất đỉnh đồ thị bắt đầu từ đỉnh Do cần thực vòng lặp để đỉnh đồ thị, nhờ không bỏ sót đỉnh Với phân tích trên, có phác thảo giải thuật duyệt đồ thị theo chiều sâu Chi tiết cho giải thuật phụ thuộc vào cách chọn lựa thực đồ thị đỉnh, để lại cho chương trình ứng dụng template void Digraph::depth_first(void (*visit)(Vertex &)) const /* post: Hàm *visit thực đỉnh đồ thị lần, theo thứ tự duyệt theo chiều sâu uses: Hàm traverse thực duyệt theo chiều sâu */ Giáo trình Cấu trúc liệu Giải thuật 347 Chương 13 – Đồ thị { bool visited[max_size]; Vertex v; for (all v in G) visited[v] = false; for (all v in G) if (!visited[v]) traverse(v, visited, visit); } Việc đệ quy thực hàm phụ trợ traverse Do hàm cần truy nhập vào cấu trúc bên đồ thị, phải hàm thành viên lớp Digraph Ngoài ra, traverse hàm phụ trợ sử dụng phương thức depth_first, nên khai báo private bên lớp template void Digraph::traverse(Vertex &v, bool visited[], void (*visit)(Vertex &)) const /* pre: v đỉnh đồ thị Digraph post: Duyệt theo chiều sâu, hàm *visit thực v tất đỉnh đến từ v uses: Hàm traverse cách đệ quy */ { Vertex w; visited[v] = true; (*visit)(v); for (all w adjacent to v) if (!visited[w]) traverse(w, visited, visit); } 13.3.3 Giải thuật duyệt theo chiều rộng Do sử dụng đệ quy lập trình với ngăn xếp chất tương đương, xây dựng giải thuật duyệt theo chiều sâu cách sử dụng ngăn xếp Khi đỉnh duyệt đỉnh kề đẩy vào ngăn xếp, đỉnh vừa duyệt xong đỉnh cần duyệt đỉnh lấy từ ngăn xếp Giải thuật duyệt theo chiều rộng tương tự giải thuật vừa đề cập đến việc duyệt theo chiều sâu, nhiên hàng đợi cần sử dụng thay cho ngăn xếp template void Digraph::breadth_first(void (*visit)(Vertex &)) const /* post: Hàm *visit thực đỉnh đồ thị lần, theo thứ tự duyệt theo chiều rộng uses: Các phương thức lớp Queue */ { Queue q; bool visited[max_size]; Vertex v, w, x; for (all v in G) visited[v] = false; Giaùo trình Cấu trúc liệu Giải thuật 348 Chương 13 – Đồ thị // Các phương thức thứ tự topo void depth_sort(List &topological_order); void breadth_sort(List &topological_order); private: int count; List neighbors[graph_size]; void recursive_depth_sort(Vertex v, bool visited[], List &topological_order); }; Hàm phụ trợ recursive_depth_sort sử dụng phương thức depth_sort Cả hai phương pháp thứ tự tạo danh sách đỉnh đồ thị theo thứ tự topo tương ứng Hình 13.7 – Các thứ tư topo đồ thị có hướng 13.4.2 Giải thuật duyệt theo chiều sâu Trong thứ tự topo, đỉnh phải xuất trước đỉnh kề đồ thị Giải thuật duyệt theo chiều sâu đặt dần đỉnh vào mảng thứ tự topo từ phải sang trái Bắt đầu từ đỉnh chưa duyệt đến, cần gọi đệ quy để đến đỉnh mà không đỉnh kề, đỉnh đặt vào mảng thứ tự topo vị trí cuối mảng Tính từ phải sang trái mảng thứ tự topo này, đỉnh kề đỉnh duyệt xong, đỉnh Giáo trình Cấu trúc liệu Giải thuật 350 Chương 13 – Đồ thị có mặt mảng Đó lúc lần gọi đệ quy bên lùi lần gọi đệ quy bên Phương pháp cách thực trực tiếp thủ tục duyệt theo chiều sâu cách tổng quát trình bày Điểm khác biệt việc xử lý đỉnh (ghi vào mảng thứ tự topo) thực sau đỉnh kề xử lý template void Digraph::depth_sort(List &topological_order) /* post: Các đỉnh đồ thị có hướng chu trình xếp theo thứ tự topo tương ứng cách duyệt đồ thị theo chiều sâu uses: Các phưong thức lớp List, hàm đệ quy recursive_depth_sort */ { bool visited[graph_size]; Vertex v; for (v = 0; v < count; v++) visited[v] = false; topological_order.clear(); for (v = 0; v < count; v++) if (!visited[v]) // Thêm đỉnh kề v sau v vào mảng tứ tự topo từ phải sang trái recursive_depth_sort(v, visited, topological_order); } Hàm phụ trợ recursive_depth_sort thực việc đệ quy, dựa phác thảo hàm traverse tổng quát, trước hết đặt tất đỉnh sau đỉnh v vào vị trí chúng thứ tự topo, sau đặt v vào template void Digraph::recursive_depth_sort(Vertex v, bool *visited, List &topological_order) /* pre: Đỉnh v chưa có mảng thứ tự topo post: Thêm đỉnh kề v sau v vào mảng tứ tự topo từ phải sang trái uses: Các phương thức lớp List hàm đệ quy recursive_depth_sort */ { visited[v] = true; int degree = neighbors[v].size(); for (int i = 0; i < degree; i++) { Vertex w; neighbors[v].retrieve(i, w); // Một đỉnh kề v if (!visited[w]) // Duyệt tiếp xuống đỉnh w recursive_depth_sort(w, visited, topological_order); } topological_order.insert(0, v); // Đặt v vào mảng thứ tự topo } Do giải thuật duyệt qua đỉnh đồ thị xác lần xem xét cạnh lần, đồng thời không thực việc tìm kiếm nào, nên thời gian chạy O(n+e), với n số đỉnh e số cạnh đồ thị Giáo trình Cấu trúc liệu Giải thuật 351 Chương 13 – Đồ thị 13.4.3 Giải thuật duyệt theo chiều rộng Trong thứ tự topo theo chiều rộng đồ thị có hướng chu trình, bắt đầu cách tìm đỉnh đỉnh thứ tự topo sau áp dụng nguyên tắc rằng, đỉnh cần phải xuất trước tất đỉnh sau thứ tự topo Giải thuật đặt đỉnh đồ thị vào mảng thứ tự topo từ trái sang phải Các đỉnh đỉnh đỉnh không đỉnh sau đỉnh đồ thị Để tìm chúng, tạo mảng predecessor_count, phần tử số v chứa số đỉnh đứng trước đỉnh v Các đỉnh cần tìm đỉnh mà đỉnh đứng trước nó, trị phần tử tương ứng mảng Các đỉnh sẵn sàng đặt vào mảng thứ tư topo Như khởi động trình duyệït theo chiều rộng cách đặt đỉnh vào hàng đỉnh xử lý Khi đỉnh cần xử lý, lấy từ hàng đặt vào vị trí mảng topo, lúc này, việc xem xét xem kết thúc đánh dấu cách cho phần tử mảng predecessor_count tương ứng với đỉnh đỉnh sau giảm Khi phần tử mảng đạt trị 0, có nghóa đỉnh tương ứng với có đỉnh trước duyệt xong, đỉnh sẵn sàng duyệt nên đưa vào hàng đợi template void Digraph::breadth_sort(List &topological_order) /* post: Các đỉnh đồ thị có hướng chu trình xếp theo thứ tự topo tương ứng cách duyệt đồ thị theo chiều rộng uses: Các phương thức lớp List, Queue */ { topological_order.clear(); Vertex v, w; int predecessor_count[graph_size]; for (v = 0; v < count; v++) predecessor_count[v] = 0; for (v = 0; v < count; v++) for (int i = 0; i < neighbors[v].size(); i++) { // Caäp nhật số đỉnh đứng trước cho đỉnh neighbors[v].retrieve(i, w); predecessor_count[w]++; } Queue ready_to_process; for (v = 0; v < count; v++) if (predecessor_count[v] == 0) ready_to_process.append(v); while (!ready_to_process.empty()) { ready_to_process.retrieve(v); topological_order.insert(topological_order.size(), v); for (int j = 0; j < neighbors[v].size(); j++) { // Giảm số đỉnh đứng trước neighbors[v].retrieve(j, w); // đỉnh kề v predecessor_count[w] ; Giáo trình Cấu trúc liệu Giải thuật 352 Chương 13 – Đồ thị if (predecessor_count[w] == 0) ready_to_process.append(w); } ready_to_process.serve(); } } Giải thuật cần đến thực lớp Queue Queue có thực theo cách mô tả chương Do phần tử Queue đỉnh Cũng duyệt theo chiều sâu, thời gian cần cho hàm breadth_first O(n+e), với n số đỉnh e số cạnh đồ thị 13.5 Giải thuật Greedy: Tìm đường ngắn 13.5.1 Đặt vấn đề Như ứng dụng khác đồ thị, xem xét toán phức tạp sau Chúng ta có đồ thị có hướng G, cạnh gán số không âm gọi tải trọng (weight) Bài toán tìm đường từ đỉnh ν đến đỉnh w cho tổng tải trọng đường nhỏ Chúng ta gọi đường đường ngắn (shortest path), tải trọng biểu diễn cho giá cả, thời gian, vài đại lượng khác thay khoảng cách Chúng ta xem G đồ tuyến bay, chẳng hạn, đỉnh đồ thị biểu diễn thành phố tải trọng cạnh biểu diễn chi phí bay từ thành phố sang thành phố Bài toán tìm lộ trình bay từ thành phố ν đến thành phố w cho tổng chi phí nhỏ Chúng ta xem xét đồ thị hình 13.8 Đường ngắn từ đỉnh đến đỉnh ngang qua đỉnh có tổng tải trọng 4, so với tải trọng cạnh nối trực tiếp từ sang 1, tổng tải trọng ngang qua đỉnh Hình 13.8 – Đồ thị có hướng với tải trọng Có thể dễ dàng giải toán cách tổng quát sau: đỉnh, gọi đỉnh nguồn, tìm đường ngắn đến đỉnh lại, thay tìm đường đến đỉnh đích Chúng ta cần tải trọng phải số không âm Giáo trình Cấu trúc liệu Giải thuật 353 Chương 13 – Đồ thị 13.5.2 Phương pháp Giải thuật thực cách nắm giữ tập S đỉnh mà đường ngắn từ đỉnh nguồn đến chúng biết Mới đầu, đỉnh nguồn đỉnh S Tại bước, thêm vào S đỉnh lại mà đường ngắn từ nguồn đến chúng vừa tìm thấy Bài toán trở thành toán xác định đỉnh để thêm vào S bước Chúng ta xem đỉnh có S tô màu đó, cạnh nằm đường ngắn từ đỉnh nguồn đến đỉnh có màu tô màu Chúng ta nắm giữ mảng distance cho biết rằng, đỉnh ν, khoảng cách từ đỉnh nguồn dọc theo đường có cạnh có màu, trừ cạnh cuối Nghóa là, ν thuộc S, distance[v] chứa khoảng cách ngắn đến ν, cạnh nằm đường tương ứng có màu Nếu ν không thuộc S, distance[v] chứa chiều dài đường từ đỉnh nguồn đến đỉnh w cộng với tải trọng cạnh nối từ w đến ν, cạnh nằm đường này, trừ cạnh cuối, có màu Mảng distance khởi tạo cách gán distance[v] với trị tải trọng cạnh nối từ đỉnh nguồn đến ν tồn cạnh này, ngược lại gán vô cực Hình 13.9 – Tìm đường ngắn Để xác định đỉnh thêm vào S bước, áp dụng “tiêu chí tham lam” (greedy criterion) việc chọn đỉnh ν có khoảng cách nhỏ từ mảng distance cho ν chưa có S Chúng ta cần chứng minh rằng, đỉnh ν này, khoảng cách chứa mảng distance thực chiều dài đường ngắn từ đỉnh nguồn đến ν Giả sử có đường ngắn từ nguồn đến ν, hình 13.9 Đường ngang đỉnh x chưa thuộc S đến ν (có thể lại qua số đỉnh khác có nằm S trước gặp ν) Nhưng đường ngắn đường tô màu đến ν, đoạn đường ban đầu từ nguồn đến x ngắn nữa, nghóa distance[x] < distance[v], tiêu chí tham lam phải chọn x thay ν đỉnh thêm vào S Giáo trình Cấu trúc liệu Giải thuật 354 Chương 13 – Đồ thị Khi thêm ν S, tô màu ν tô màu đường ngắn từ đỉnh nguồn đến ν (mọi cạnh trừ cạnh cuối thực tô màu trước Hình 12.10 - Ví dụ đường ngắn đó) Kế tiếp, cần cập nhật lại phần tử mảng distance cách xem xét đỉnh w nằm S, đường từ nguồn qua ν đến w có ngắn khoảng cách từ nguồn đến w ghi nhận trước hay không Nếu điều xảy có nghóa vừa phát đường cho đỉnh w có ngang qua ν ngắn cách xác định trước đó, cần cập nhật lại distance[w] distance[v] cộng với tải trọng cạnh nối từ ν đến w Giáo trình Cấu trúc liệu Giải thuật 355 Chương 13 – Đồ thị 13.5.3 Ví dụ Trước viết hàm cho phương pháp này, xem qua ví dụ hình 13.10 Đối với đồ thị có hướng hình (a), trạng thái ban đầu hình (b): tập S (các đỉnh tô màu) gồm có nguồn đỉnh 0, phần tử mảng distance chứa số đặt cạnh đỉnh lại Khoảng cách đến đỉnh ngắn nhất, nên thêm vào S hình (c), distance[3] cập nhật thành Do khoảng cách đến ngang qua lớn khoảng cách chứa mảng distance, nên khoảng cách distance giữ không đổi Đỉnh gần nguồn đỉnh 2, thêm vào S hình (d), khoảng cách đến đỉnh cập nhật lại ngang qua đỉnh Hai bước cuối cùng, hình (e) (f), đỉnh thêm vào đường ngắn từ đỉnh nguồn đến đỉnh lại sơ đồ cuối 13.5.4 Hiện thực Để thực giải thuật tìm đường ngắn trên, cần chọn cách thực cho đồ thị có hướng Việc dùng bảng kề cho phép truy xuất ngẫu nhiên đến đỉnh đồ thị Hơn nữa, sử dụng bảng vừa để chứa tải trọng vừa để chứa thông tin đỉnh kề Trong đặc tả đồ thị có hướng, thêm thông số template cho phép người sử dụng chọn lựa kiểu tải trọng theo ý muốn Lấy ví dụ, người sử dụng dùng lớp Digraph để mô hình hóa mạng tuyến bay, họ chứa giá vé số nguyên hay số thực template class Digraph { public: // Thêm constructor phương thức nhập xuất đồ thị void set_distances(Vertex source, Weight distance[]) const; protected: int count; Weight adjacency[graph_size][graph_size]; }; Thuộc tính count chứa số đỉnh đối tượng đồ thị Trong ứng dụng, cần bổ sung phương thức để nhập hay xuất thông tin đối tượng đồ thị, chúng không cần thiết việc thực phương thức distance để tìm đường ngắn nhất, xem chúng tập Chúng ta giả sử lớp Weight có tác vụ so sánh Ngoài ra, người sử dụng phải khai báo trị lớn có Weight, gọi infinity Chẳng hạn, chương trình người sử dụng với tải trọng số nguyên sử dụng thư viện chuẩn ANSI C++ với định nghóa toàn cục sau: const Weight infinity = numeric_limits::max(); Giáo trình Cấu trúc liệu Giải thuật 356 Chương 13 – Đồ thị Chúng ta đặt trị infinity vào phần tử mảng distance tương ứng với cạnh từ không tồn nguồn đến đỉnh Phương thức set_distance tìm đường ngắn khoảng cách ngắn trả qua tham biến distance[] template void Digraph::set_distances(Vertex source, Weight distance[]) const /* post: Mảng distance chứa đường có tải trọng ngắn từ đỉnh nguồn đến đỉnh đồ thị */ { Vertex v, w; bool found[graph_size]; // Biểu diễn đỉnh S for (v = 0; v < count; v++) { found[v] = false; distance[v] = adjacency[source][v]; } found[source]=true;// Khởi tạo cách bỏ đỉnh nguồn vào S distance[source] = 0; for(int i = 0; i < count; i++){ // Mỗi lần lặp bỏ thêm đỉnh vào S Weight = infinity; for (w = 0; w < count; w++) if (!found[w]) if (distance[w] < min) { v = w; = distance[w]; } found[v] = true; for (w = 0; w < count; w++) if (!found[w]) if (min + adjacency[v][w] < distance[w]) distance[w] = + adjacency[v][w]; } } Để ước đoán thời gian cần để chạy hàm này, thấy vòng lặp thực n-1 lần, n số đỉnh, bên vòng lặp có hai vòng lặp khác, vòng lặp thực n-1 lần Vậy vòng lặp thực (n-1)2 lần Các lệnh bên vòng lặp hết O(n), nên thời gian chạy hàm O(n2) 13.6 Cây phủ tối tiểu 13.6.1 Đặt vấn đề Giải thuật tìm đường ngắn phần áp dụng với mạng hay đồ thị có hướng mạng hay đồ thị hướng Ví dụ, hình 13.11 minh họa ứng dụng tìm đường ngắn từ đỉnh nguồn đến đỉnh khác mạng Giáo trình Cấu trúc liệu Giải thuật 357 Chương 13 – Đồ thị Hình 13.11 – Tìm đường ngắn mạng Nếu mạng dựa sở đồ thị liên thông G, đường ngắn từ đỉnh nguồn nối nguồn với tất đỉnh khác G Từ đó, hình 13.11, kết hợp đường ngắn tính lại với nhau, có nối tất đỉnh G Nói cách khác, tạo tất đỉnh số cạnh đồ thị G Chúng ta gọi phủ (spanning tree) G Cũng phần trước, xem mạng đồ thị G đồ tuyến bay, với đỉnh biểu diễn thành phố tải trọng cạnh giá vé bay từ thành phố sang thành phố Một phủ G biểu diễn tập đường bay cho phép hành khách hoàn tất chuyến du lịch qua khắp thành phố Dó nhiên rằng, hành khách cần phải thực số tuyến bay vài lần hoàn tất chuyến du lịch Điều bất tiện bù đắp chi phí thấp Nếu hình dung mạng hình 13.11 hệ thống điều khiển tập trung, đỉnh nguồn tương ứng với sân bay trung tâm, đường từ đỉnh hành trình bay Một điều quan trọng sân bay theo hệ thống điều khiển tập trung giảm tối đa chi phí cách chọn lựa hệ thống đường bay mà tổng chi phí nhỏ Định nghóa: Một phủ tối tiểu (minimal spanning tree) mạng liên thông phủ mà tổng tải trọng cạnh nhỏ Mặc dù việc so sánh hai phủ hình 13.12 không khó khăn, khó mà biết có phủ có trị nhỏ hay không Bài toán xây dựng phương pháp xác định phủ tối tiểu mạng liên thông Giáo trình Cấu trúc liệu Giải thuật 358 Chương 13 – Đồ thị Hình 13.12 – Hai phủ mạng 13.6.2 Phương pháp Chúng ta biết giải thuật tìm phủ đồ thị liên thông, giải thuật tìm đường ngắn có Chúng ta thay đổi chút giải thuật tìm đường ngắn để có phương pháp tìm phủ tối tiểu mà R C Prim đưa lần đầu vào năm 1957 Trước hết chọn đỉnh bắt đầu, gọi nguồn, tiến hành phương pháp, nắm giữ tập X đỉnh mà đường từ chúng đến đỉnh nguồn thuộc phủ tối tiểu mà tìm thấy Chúng ta cần nắm tập Y gồm cạnh nối đỉnh X mà thuộc xây dựng Như vậy, hình dung đỉnh X cạnh Y tạo phần mà cần tìm, lớn lên thành phủ tối tiểu cuối Lúc khởi đầu, đỉnh nguồn đỉnh X, Y tập rỗng Tại bước, thêm đỉnh vào X: đỉnh chọn cho có cạnh nối với đỉnh có X có tải trọng nhỏ nhất, so với tải trọng tất cạnh khác nối đỉnh nằm X với đỉnh có X Cạnh tối tiểu đưa vào Y Việc chứng minh giải thuật Prim đem lại phủ tối tiểu không thực dễ dàng, tạm hoãn việc lại sau Tuy nhiên, hiểu việc chọn lựa đỉnh để thêm vào X cạnh để thêm vào Y Tiêu chí Prim cho cách dễ dàng để thực việc kết nối này, vậy, theo tiêu chí tham lam, nên sử dụng Khi thực giải thuật Prim, cần nắm giữ danh sách đỉnh thuộc X phần tử mảng kiểu bool Cách nắm giữ cạnh Y dễ dàng bắt chước cách lưu trữ cạnh đồ thị Giáo trình Cấu trúc liệu Giải thuật 359 Chương 13 – Đồ thị Chúng ta cần đến mảng phụ neighbor để biết thêm rằng, đỉnh ν, đỉnh X có cạnh đến ν có tải trọng nhỏ Các tải trọng nhỏ chứa mảng distance tương ứng Nếu đỉnh ν cạnh nối với đỉnh X, trị phần tử tương ứng distance infinity Hình 13.13 – Ví dụ giải thuật Prim Mảng neighbor khởi tạo cách gán neighbor[v] đến đỉnh nguồn cho tất đỉnh ν, distance khởi tạo cách gán distance[v] tải trọng cạnh nối từ đỉnh nguồn đến ν infinity cạnh không tồn Để xác định đỉnh thêm vào tập X bước, chọn đỉnh ν số đỉnh chưa có X mà trị tương ứng mảng distance nhỏ Sau cần cập nhật lại mảng để phản ánh thay đổi mà làm tập X sau: với w chưa có X, có Giáo trình Cấu trúc liệu Giải thuật 360 Chương 13 – Đồ thị cạnh nối ν w, xem thử tải trọng cạnh có nhỏ distance[w] hay không, distance[w] cần cập nhật lại trị tải trọng này, neighbor[w] ν Lấy ví dụ, xem xét mạng hình (a) hình 13.13 Trạng thái ban đầu hình (b): Tập X (các đỉnh tô màu) gồm đỉnh nguồn 0, đỉnh w, đỉnh lưu neighbor[w] mũi tên từ w Trị distance[w] tải trọng cạnh tương ứng Khoảng cách từ nguồn đến đỉnh trị nhỏ nhất, nên thêm vào X hình (c), phần tử mảng neighbor distance có phần tử tương ứng với đỉnh đỉnh cập nhật lại Đỉnh gần với đỉnh X đỉnh 2, thêm vào X hình (d), trị mảng cập nhật lại Các bước cuối minh họa hình (e), (f) (g) 13.6.3 Hiện thực Để thực giải thuật Prim, cần chọn lớp để biểu diễn cho mạng Sự tương tự giải thuật so với giải thuật tìm đường ngắn phần trước giúp định thiết kế lớp Network dẫn xuất từ lớp Digraph template class Network: public Digraph { public: Network(); void read(); // Định nghóa lại để nhập thông tin mạng void make_empty(int size = 0); void add_edge(Vertex v, Vertex w, Weight x); void minimal_spanning(Vertex source, Network &tree) const; }; Chúng ta viết lại phương thức nhập read để đảm bảo tải trọng cạnh (ν, w) trùng với tải trọng cạnh (w, ν), với ν w, mạng vô hướng Phương thức make_empty(int size) tạo mạng có size đỉnh cạnh Phương thức khác, add_edge, thêm cạnh có tải trọng cho trước vào mạng Chúng ta giả sử lớp Weight có đầy đủ toán tử so sánh Ngoài ra, người sử dụng cần khai báo trị lớn Weight gọi infinity Phương thức minimal_spanning mà viết tìm phủ tối tiểu trả qua tham biến tree Tuy phương thức tìm phủ thực mạng liên thông, tìm phủ cho thành phần liên thông có chứa đỉnh source mạng Giáo trình Cấu trúc liệu Giải thuật 361 Chương 13 – Đồ thò template void Network::minimal_spanning(Vertex source, Network &tree) const /* post: Xác định phủ tối tiểu thành phần liên thông có chứa đỉnh source mạng */ { tree.make_empty(count); bool component[graph_size]; // Các đỉnh tập X Vertex neighbor[graph_size];// Phần tử thứ i chứa đỉnh trước cho khoảng // cách chúng nhỏ so với khoảng cách từ // đỉnh trước khác có tập X đến Weight distance[graph_size];// Các khoảng cách nhỏ tương ứng với phần tử // mảng neighbor Vertex w; for (w = 0; w < count; w++) { component[w] = false; distance[w] = adjacency[source][w]; neighbor[w] = source; } component[source] = true; // Tập X có đỉnh nguồn for (int i = 1; i < count; i++) { Vertex v; // Mỗi lần lặp bổ sung thêm đỉnh vào tập X Weight = infinity; for (w = 0; w < count; w++) if (!component[w]) if (distance[w] < min) { v = w; = distance[w]; } if (min < infinity) { component[v] = true; tree.add_edge(v, neighbor[v], distance[v]); for (w = 0; w < count; w++) if (!component[w]) if (adjacency[v][w] < distance[w]) { distance[w] = adjacency[v][w]; neighbor[w] = v; } } else break; // Xong thành phần liên thông đồ thị không liên thông } } Vòng lặp hàm thực n-1 lần, với n số đỉnh, vòng lặp có hai vòng lặp khác, vòng lặp thực n-1 lần Vậy vòng lặp thực (n-1)2 lần Các lệnh bên vòng lặp hết O(n), nên thời gian chạy hàm O(n2) 13.6.4 Kiểm tra giải thuật Prim Chúng ta cần chứng minh rằng, đồ thị liên thông G, phủ S sinh giải thuật Prim phải có tổng tải trọng cạnh nhỏ so với Giáo trình Cấu trúc liệu Giải thuật 362 Chương 13 – Đồ thị phủ khác G Giải thuật Prim xác định chuỗi cạnh s1, s2, , sn tạo phủ S Như hình 13.14, s1 cạnh thứ thêm vào tập Y giải thuật Prim, s2 cạnh thứ hai thêm vào, Để chứng minh S phủ tối tiểu, chứng minh m số nguyên, ≤ m ≤ n, có phủ tối tiểu chứa cạnh si với i ≤ m Chúng ta chứng minh quy nạp m Trường hợp bản, m = 0, rõ ràng đúng, phủ tối tiểu phải chứa tập rỗng cạnh Ngoài ra, hoàn tất việc quy nạp, trường hợp cuối với m = n có phủ tối tiểu chứa tất cạnh S, S (Lưu ý thêm cạnh vào phủ tạo chu trình, nên phủ chứa cạnh S phải S) Nói cách khác, hoàn tất việc quy nạp, chứng minh S phủ tối tiểu Như cần trình bày bước quy nạp cách chứng minh m < n T phủ tối tiểu chứa cạnh s i với i ≤ m, phải có phủ tối tiểu U chứa cạnh thêm cạnh sm+1 Nếu sm+1 có T, đơn giản cho U = T, giả sử sm+1 không cạnh T Chúng ta xem hình (b) hình 13.14 Hình 13.14 – Kiểm tra giải thuật Prim Chúng ta gọi X tập đỉnh S thuộc cạnh s1, s2, , sm R tập đỉnh lại S Trong giải thuật Prim, cạnh sm+1 nối đỉnh Giáo trình Cấu trúc liệu Giải thuật 363 Chương 13 – Đồ thị X với đỉnh R, sm+1 cạnh có tải trọng nhỏ nối hai tập Chúng ta xét ảnh hưởng việc thêm cạnh sm+1 vào T, minh họa hình (c) Việc thêm vào phải tạo chu trình C, mạng liên thông T rõ ràng có chứa đường có nhiều cạnh nối hai đầu cạnh sm+1 Chu trình C phải có chứa cạnh t ≠ sm+1 nối tập X với tập R, di dọc theo đường khép kín C phải vào tập X số lần với số lần khỏi Chúng ta xem hình (d) Giải thuật Prim bảo đảm tải trọng sm+1 nhỏ tải trọng t Do đó, phủ U hình (e), có từ T cách loại t thêm vào sm+1, có tổng tải trọng không lớn tải trọng T Như có U phủ tối tiểu G, mà U chứa cạnh s1, s2, , sm, sm+1 Điều hoàn tất trình quy nạp 13.7 Sử dụng đồ thị cấu trúc liệu Trong chương này, tìm hiểu ứng dụng đồ thị, bắt đầu thâm nhập vào vấn đề sâu sắc giải thuật đồ thị Nhiều giải thuật số đó, đồ thị xuất cấu trúc toán học nắm bắt đặc trưng thiết yếu toán, thay công cụ tính toán cho lời giải chúng Lưu ý chương nói đồ thị cấu trúc toán học, không cấu trúc liệu, sử dụng chúng để đặc tả vấn đề toán học, để viết giải thuật, thực đồ thị cấu trúc liệu danh sách bảng Tuy vậy, rõ ràng đồ thị tự thân xem cấu trúc liệu - cấu trúc liệu mà có chứa mối quan hệ liệu phức tạp mô tả danh sách Do tính tổng quát mềm dẻo, đồ thị cấu trúc liệu hiệu tỏ rõ giá trị ứng dụng cấp tiến thiết kế hệ quản trị sở liệu chẳng hạn Tất nhiên, công cụ mạnh nên sử dụng cần thiết, việc sử dụng cần phải kết hợp với việc xem xét cách cẩn thận để sức mạnh không làm cho cho bị rối Cách an toàn việc sử dụng công cụ mạnh dựa quy; nghóa là, sử dụng công cụ mạnh phương pháp định nghóa cách cẩn thận dễ hiểu Do tính tổng quát đồ thị, việc tuân thủ nguyên tắc vừa nêu việc sử dụng dễ dàng Giáo trình Cấu trúc liệu Giải thuật 364 ... thị cấu trúc toán học, không cấu trúc liệu, sử dụng chúng để đặc tả vấn đề toán học, để viết giải thuật, thực đồ thị cấu trúc liệu danh sách bảng Tuy vậy, rõ ràng đồ thị tự thân xem cấu trúc liệu. .. liệu - cấu trúc liệu mà có chứa mối quan hệ liệu phức tạp mô tả danh sách Do tính tổng quát mềm dẻo, đồ thị cấu trúc liệu hiệu tỏ rõ giá trị ứng dụng cấp tiến thiết kế hệ quản trị sở liệu chẳng... xong Giáo trình Cấu trúc liệu Giải thuật 346 Chương 13 – Đồ thị Hình 13. 6 minh họa hai phương pháp duyệt trên, số đỉnh biểu diễn thứ tự mà chúng duyệt đến Hình 13. 6 - Duyệt đồ thị 13. 3.2 Giải thuật