1. Trang chủ
  2. » Luận Văn - Báo Cáo

Giải Thuật Lập Trình

11 2 0

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 11
Dung lượng 292,65 KB

Nội dung

Giải Thuật Lập Trình Nơi tổng hợp chia sẻ kiến thức liên quan tới giải thuật nói chung lý thuyết khoa học máy tính nói riêng ‹ Các kĩ thuật xử lí bít I Bitwise tricks I • DFS, phân loại cung xếp Topo DFS, Arc Classification and Topological Sort › Đồ thị Introduction to Algorithmic Graph Theory September 26, 2015 in Uncategorized | No comments Trong loạt làm quen với đồ thị thuật toán với đồ thị Đồ thị đối tượng tổ hợp (combinatorial object) nghiên cứu ứng dụng nhiều thực tế (có lẽ thừa viết điều này) Phần sẽ: Làm quen với khái niệm gắn với đồ thị Cách biểu diễn đồ thị máy tính để thao tác với Duyệt đồ thị theo chiều rộng (Breadth First Search) Duyệt đồ thị theo chiều sâu (Depth First Search) Bạn đọc bỏ qua phần mà bạn quen thuộc Note [1] Jeff Erickson tài liệu tham khảo Các khái niệm Một đồ thị, kí hiệu G(V , E), gồm hai thành phần: V Tập hợp , bao gồm đối tượng, gọi tập hợp đỉnh (vertex) đồ thị E V Tập hợp ⊆ , bao gồm cặp đỉnh, gọi tập hợp cạnh (vertex) đồ thị nm m Ta kí hiệu , số đỉnh số cạnh đồ thị, i.e, | | = , | | = Số đỉnh đồ thị ta gọi bậc đồ thị (order of the graph) V n E uvxyz Các đỉnh ta kí hiệu chữ in thường , , , , Cạnh hai đỉnh , vơ hướng có hướng Trong trường hợp đầu ta kí hiệu cạnh , cịn trường hợp sau ta kí hiệu → để rõ hướng cạnh từ đến Thơng thường ta nói cạnh ta ám cạnh vơ hướng cịn với cạnh có hướng ta gọi cung (arc) Hình (1, 2) hình biểu diễn đồ thị vô hướng uv uv u v u v (các cạnh vơ hướng) hình (3) phải hình biểu diễn đồ thị có hướng aa Trong hình (1), cạnh ( , ) gọi cạnh lặp (loop) hai cạnh bd cặp đỉnh ( , ) gọi hai cạnh song song (parallel edges) Một đồ thị gọi đơn đồ thị (simple graph) khơng có cạnh lặp cạnh song song (hình (2)) Nếu đồ thị khơng phải đơn đồ thị goị đa đồ thị (multigraph) Trong loạt đồ thị đây, ta chủ yếu xét đơn đồ thị Do đó, nói đồ thị ta ngầm hiểu đơn đồ thị Ta có: Fact 1: Nếu G(V , E) đơn đồ thị vơ hướng m ≤ n(n−1) G(V , E) vô hướng, với cạnh uv, đỉnh v gọi kề uv u v v dv v Nếu đồ thị G(V , E) có hướng, với cung u → v, đỉnh v gọi đỉnh liền sau (successor) u đỉnh u gọi đỉnh liền trước (predecessor) v Bậc tới (in-degree) v số đỉnh liền trước v bậc lui (out-degree) v số đỉnh liền sau v Ví dụ bậc tới d Nếu đồ thị (incident) với cạnh Đỉnh gọi hàng xóm (neighbor) Bậc (degree) đỉnh , thường kí hiệu ( ), số hàng xóm đỉnh hình (3) bậc lui Ta gọi H (VH , EH ) đồ thị (subgraph) G VH ⊆ V EH ⊆ E Một đường (walk) dãy cạnh {e , e , … , ek } hai cạnh liền kề ei ei có chung đỉnh +1 Chú ý đường đi qua đỉnh nhiều lần Trong trường hợp đỉnh thăm lần, ta gọi đường đơn (path) Ví dụ đồ hình đây, { , , , , , } đường ab bc ca ae ed dc a c {ab, bd, dc} đường đơn a c Một đường đóng (closed walk) đường bắt đầu kết thục điểm Một chu trình (cycle) đường đơn bắt đầu kết thúc điểm Có thể nói chu trình đường đóng qua điểm lần ngoại trừ điểm đầu điểm cuối Các khái niệm vừa áp dụng cho đồ thị có hướng ta thêm từ "có hướng" vào đằng trước Một đồ thị vô hướng gọi liên thông (connected) tồn đường cặp điểm Một đồ thị có hướng gọi liên thơng (yếu) đồ thị vơ hướng thu từ đồ thị cách bỏ qua hướng cạnh liên thông Một đồ thị có hướng gọi liên thơng mạnh (strongly connected) tồn đường có hướng cặp điểm Hiển nhiên đồ thị có hướng liên thơng mạnh liên thơng yếu Tuy nhiên điều ngược lại chưa (ví dụ?) Ví dụ đồ thị (1) khơng liên thông, đồ thị (2) liên thông, đồ thị (3) liên thông yếu (nhưng không mạnh) đồ thị (4) liên thông mạnh Nếu đồ thị (vô hướng) không liên thông, tập đỉnh liên thông với tạo thành thành phần liên thông (connected component) Tương tự ta định nghĩa thành phần liên thơng (yếu hay mạnh) cho đồ thị có hướng Một đồ thị khơng có chu trình (acyclic) ta gọ rừng (forest) Một rừng có thành phần liên thơng ta gọi (tree) Khái niệm rừng có hướng tương tự đồ thị có hướng GV E m n Fact 2: Nếu ( , ) ≤ − rừng m = n − Nếu G(V , E) Có lẽ ta dừng định nghĩa khái niệm Còn rất nhiều khái niệm khác định nghĩa mà cần Gần tất khái niệm liệt kê [2] mà bạn đọc tham khảo thêm Trong phần tiếp theo, ta xét G(V , E) vô hướng Các thao tác với đồ thị có hướng mở rộng áp dụng cách tương tự Biểu diễn đồ thị Chúng ta biểu diễn đồ thị ma trận kề (adjacency matrix) có kích thước × đó: A n n uv E A[u, v] = { 1, 0, if  ∈ otherwise  (1) Có thể thấy kích thước cách biểu diễn m O(n ) số lượng cạnh nhiều hay Theo Fact 1, số lượng cạnh đồ thị lên tới ( ) cạnh (ta gọi đồ thị dầy) Do đó, cách biểu diễn On nói phù hợp với đồ thị dầy Tuy nhiên, nhiều đồ thị (đặc biệt = ( ) (ta gọi đồ đồ thị thực tế mạng xã hôi), số lượng cạnh m On thị thưa) Do cách biểu diễn tốn với đồ thị thưa u V Để tiết kiệm nhớ, với đỉnh ∈ , ta lưu trữ danh sách đỉnh kề với Như vậy, đỉnh cần danh sách có ( ) phần tử Do tổng số phần tử danh sách là: u Tổng ∑u V d v ∈ ∑u V d v ∈ ( )=2 ( )=2 m du (2) m cạnh đếm hai lần tổng bậc hai đỉnh kề với Cách biểu diễn gọi biểu diễn danh sách kề (adjacency list) Cách biểu diễn phù hợp với đồ thị thưa Mặc dù tiết kiệm nhớ, cách biểu diễn không phù hợp với số thao tác đồ thị Bảng so sánh hai cách biểu diễn vừa trình bày Adjacency matrix Space Test O( n ) O(1) O( n ) O(1) O(1) uv ∈ E List all neighbors of uv Delete an edge uv Add an edge v Adjacency list (linked list) O( n + m ) O(1 + min(d(u), d(v))) O(1 + d(v)) O(1) O(d(u) + d(v)) = O(n) Ví dụ hai cách biểu diễn đồ thị cho hình đây: Ta cịn kết hợp cách biểu diễn danh sách kề với vài cấu trúc liệu khác Cụ thể, thay dùng danh sách liên kết để biểu diễn đỉnh kề với đỉnh , ta cịn dùng bảng băm cấu trúc để biểu diễn Trong khn khổ viết đây, ta dùng (hoặc không dùng) cấu trúc u uv Ngồi ta biểu diễn đồ thị cách liệt kê tất cặp ( , ) thỏa mãn uv ∈ E Cách biểu diễn có nhớ O(m) Tuy nhiên, việc thực thao tác cách biểu diễn tốn Đơi khi, ta kết hợp cách biểu diễn với cách biểu diễn danh sách kề để tận dụng ưu hai cách biểu diễn mà nhớ tuyến tính Duyệt đồ thị GV E Problem 1: Cho đồ thị ( , ) đỉnh thỏa mãn tồn đường từ tới v s v s ∈ V , in đỉnh s Ta gọi toán toán duyệt đồ thị từ đỉnh Để đơn giản, ta giả sử đồ thị liên thông Trường hợp đồ thị không liên thông mở rộng cuối phần Cách thức chung để duyệt đồ thị sau: Ta sử dụng loại nhãn để gán cho đỉnh đồ thị: chưa thăm (unvisited) thăm (visited) Ban đầu tất đỉnh đánh dấu chưa thăm (unvisited) Ta trì tập hợp (thực thi tập ta tìm hiểu sau), ban đầu khởi tạo rỗng Ta thực lặp bước sau: C Lấy đỉnh u C (thủ tục R C C ( ) đây) u thăm (visited) Đưa hàng xóm u có nhãn chưa thăm vào C Thủ tục A (C , v) đưa đỉnh v vào tập C Thuật toán dừng C = ∅ Giả mã sau: Đánh dấu G G V E), s): G T ( ( , mark all vertices unvisited A ( , ) Cs C ≠∅ while ←R ( ) ≪ (∗) ≫ if is unvisited mark visited ∈ for all and is unvisited u C u A u uv E (C , v ) v ≪ (∗∗) ≫ C C uvw Remark: Một đỉnh đưa nhiều lần vào tập (do khơng tập hợp có nhiều phần tử giống nhau) Ví dụ xét đỉnh , , đôi kề Đỉnh lấy từ đầu tiên; đánh dấu thăm Ngay sau đó, đưa vào Tiếp theo, lấy khỏi đánh dấu thăm Lúc ta lại tiếp tục đưa vào lần theo giả mã trên, hàng xóm có nhãn chưa thăm Ở đây, ta không kiểm tra xem đỉnh nằm hay chưa trước đưa vào v v u C w w v C Từ giả mã trên, ta thấy, tập thăm C u v C w C C C lưu đỉnh kề với đỉnh Phân tích thuật toán: Giả sử ta sử dụng cấu trúc để thực thi cho việc thêm vào lấy đỉnh (dịng (∗) dịng cuối C O C cùng) thực thời gian (1) (ví dụ thưc thi danh sách liên kết thêm vào lấy đỉnh đầu danh sách thực thời gian (1)) Ta có vài nhận xét sau: O C bị đánh dấu thăm khơng C Mỗi lần đỉnh v đưa vào C , hàng xóm bị đánh dấu thăm Do đó, đỉnh v bị đưa vào C không d(v) lần Mỗi lấy đỉnh u khỏi C , ta duyệt qua tất hàng xóm u Thao tác thời gian O(d(u)) Theo nhận xét 1, phép duyệt Các đỉnh lấy khỏi đưa trở lại tập thực tối đa lần O(∑u V d(u)) = O(m) Từ nhận xét trên, ta suy tổng thời gian tính tốn thuật tốn ∈ Trong trường hợp đồ thị không liên thông, ta phải duyệt qua thành phần liên thông Do đồ thị có tối đa thành phần liên thơng, ta có: n Theorem 1: Ta duyệt qua đồ thị O( n + m ) C G(V , E) thời gian Nếu ta thực thi danh sách liên kết có lẽ khơng có thú vị Tuy nhiên, ta thực thi hàng đợi (Queue) ngăn xếp (Stack) ta thu số tính chất thú vị từ đồ thị Trường hợp ta thực thi hàng đợi, ta gọi thuật toán duyệt theo chiều rộng (Breath First Search - BFS) Trường hợp ta thực thi ngăn xếp, ta gọi thuật toán duyệt theo chiều sâu (Depth First Search - DFS) Sau ta thảo luận hai thuật toán C C C Thuật toán duyệt theo chiều rộng BFS C Như nói trên, thuật tốn BFS thực thi hàng đợi Ta thay thủ tục A ( , ) thủ tục E ( , ) thủ tục R ( ) thủ tục D ( ) Ngoài khung thuật toán trên, ta Cv gán cho đỉnh C Cv C v nhãn d[v] Giá trị d[v], ta s tới v Giả mã sau: đây, khoảng cách ngắn từ GV E s v V dv BFS( ( , ), ): for each ∈ [ ] ← +∞ ← an empty Queue E ( , ) [ ]←0 C Cs ds C ≠∅ while ←D ( ) if is unvisited mark visited ∈ for all and u u C u uv E v is unvisited (C , v) E d [v ] ← d [u ] + ≪ (∗∗) ≫ Code giả mã C: #define #define #define #define #define UNVISITED VISITED TRUE FALSE INFTY 1000000 ? (# 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 // the vertex list data structure, it is a linked list typedef struct vlist{ int v; // vertex v is adjacent to the index of the li struct vlist *next; }vlist; vlist **G; int n; int m; int *D; int* mark; // // // // the number of veritces the number of edges the distance of vertices from BFS an array to mark visited vertices typedef struct Queue{ int *storage; int back, front; }Queue; void bfs(int s){ printf("executing bfs \n"); D = (int *)(malloc(n*sizeof(int))); int i = 0; for(; i < n; i++) D[i] = INFTY; Queue *Q = init_queue(n); enqueue(Q,s); D[s] = 0; int u; int v; while(!is_queue_empty(Q)){ u = dequeue(Q); if(mark[u] == UNVISITED){ printf("visiting %d\n",u); mark[u] = VISITED; // loop through adjaceny list of u vlist *runner = G[u]; while(runner != NULL){ v = runner->v; if(mark[v] == UNVISITED){ D[v] = D[u]+1; enqueue(Q,v); } runner = runner->next; } } } printf("done bfs\n"); } ////////////////////////////////////////////////////////////// //////////////// //////////////// THE QUEUE INTERFACES //////////////// ////////////////////////////////////////////////////////////// Queue *init_queue(int size){ Queue *Q = malloc(sizeof(Queue)); Q->storage = (int *)malloc(size*sizeof(int)); Q->front = 0; Q->back = -1; return Q; } void enqueue(Queue *Q, int elem){ Q->back ++; Q->storage[Q->back] = elem; } int dequeue(Queue *Q){ if(is_queue_empty(Q)) { printf("nothing to dequeue\n"); exit(0); } int elem = Q->storage[Q->front]; 77 78 79 80 81 82 83 Q->front++; return elem; } int is_queue_empty(Queue* Q){ return Q->back < Q->front? TRUE : FALSE; } Ví dụ ta thực thi thuật tốn với đồ thị hình bên trái kết qủa thu hình bên phải Các số ứng với đỉnh tương ứng nhãn đỉnh Những cạnh màu đỏ laf cạnh mà có nhãn unvisited dòng (∗∗) thăm BFS v Theorem 2: Nhãn đỉnh thu sau duyệt BFS khoảng cách ngắn từ đỉnh xuất phát tới đỉnh s u, kí hiệu level(u), khoảng cách ngắn từ đỉnh s tới u Trong ví dụ trên, đỉnh b, c, e có mức 1, d, h có mức 2, Bằng quy nạp, ta chứng minh (coi Chứng minh: Gọi mức (level) đỉnh tập cho bạn đọc): Claim: Các đỉnh mức 1, 2, … , − i i thăm sau đỉnh mức level v level v d v Xét đỉnh v Lần v đưa vào hàng đợi C ta thăm đỉnh u kề với v theo Claim trên, mức u nhỏ mức v Từ định nghĩa mức ta suy level(u) = level(v) − Theo giả thiết quy nạp d[u] = level(u), ta suy level(v) = d[u] + Theo giả mã, đưa v vào hàng đợi, ta cập nhật d[v] = d[u + 1] Do đó, d[v] = lelve(v), dpcm Ta quy nạp biến để chứng minh nhãn ( ) = [ ] mức , i.e, v Kết hợp Theorm Theorem ta có hệ sau: On m Corollary 1: Trong thời gian ( + ), ta tìm khoảng cách ngắn từ đỉnh tới đỉnh khác đồ thị vơ hướng khơng có trọng số Corollary có ý nghĩa lớn ta thấy (trong thuật toán Dijkstra (http://www.giaithuatlaptrinh.com/?p=764) ), thuật toán tốt tìm đường ngắn với đồ thị có số có thời gian O(n log n) đồ thị thưa (m = O(n)) Thuật toán BFS thuật toán đơn giản hiệu để tìm đường ngắn đồ thị khơng có số Thuật toán duyệt theo chiều sâu DFS C Cv Trong duyệt theo chiều sâu DFS, ta thực thi sử dụng ngăn xếp Ta thay thủ tục A ( , ) thủ tục P ( , ) thủ tục R ( ) thủ tục P Cv C ( ) C GV E s DFS( ( , ), ): ← an empty Stack P ( , ) C Cs while C ≠ ∅ u ← P (C ) if u is unvisited mark u visited for all uv ∈ E and v is unvisited P (C , v ) ≪ (∗∗) ≫ Code giả mã C: 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 #define #define #define #define #define UNVISITED VISITED TRUE FALSE INFTY 1000000 ? (# // the vertex list data structure, it is a linked list typedef struct vlist{ int v; // vertex v is adjacent to the index of the li struct vlist *next; }vlist; vlist **G; int n; int m; int *D; int* mark; // // // // the number of veritces the number of edges the distance of vertices from BFS an array to mark visited vertices typedef struct Stack { int *storage; int top; } Stack; void dfs(int s){ printf("executing dfs .\n"); Stack *S = init_stack(n); push(S, s); int u; int v; while(!is_stack_empty(S)){ u = pop(S); if(mark[u] == UNVISITED){ printf("visiting %d\n",u); mark[u] = VISITED; // loop through neighbors of u vlist *runner = G[u]; // the ajdacency list while(runner!= NULL){ v = runner->v; if(mark[v] == UNVISITED){ push(S,v); } runner = runner->next; 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 } } } printf("done dfs\n"); } ////////////////////////////////////////////////////////////// //////////////// //////////////// THE STACK INTERFACES //////////////// ////////////////////////////////////////////////////////////// Stack *init_stack(int size){ Stack *S = (Stack *)malloc(sizeof(Stack)); S->top = -1; S->storage = (int*)malloc(size*sizeof(int)); return S; } void push(Stack *S, int elem){ S->top++; S->storage[S->top] = elem; } int pop(Stack *S){ if(is_stack_empty(S)){ printf("nothing to pop\n"); exit(0); } int elem = S->storage[S->top]; S->top ; return elem; } int is_stack_empty(Stack *S){ return S->top < ? TRUE: FALSE; } Nếu nhìn qua thi không thấy khác biệt qúa nhiều DFS thuật toán chung để duyệt đồ thị Tuy nhiên cách cập nhật thêm vài thông tin q trình duyệt đồ thị (giống BFS), ta phát tính chất thú vị DFS Ta thảo luận tính chất sau Hình ví dụ thực thi DFS đồ thị Số tương ứng đỉnh bên phải thứ tự đỉnh thăm DFS Ngoài thủ tục lặp DFS sử dụng ngăn xếp trên, có lẽ số quen thuộc với thủ tục thực thi DFS sử dụng đệ quy sau: R s s sv ∈ E DFS( ): mark visited for all and is unvisited R DFS( ) v v Code giả mã 10 11 12 void recursive_dfs(int s){ printf("visiting %d\n",s); mark[s] = VISITED; // loop through neighbors of s int v = 0; for(; v < n; v++){ if(G[s][v] == && mark[v] == UNVISITED){ recursive_dfs(v); } } ? (# } Ta thấy cách thứ hai đơn giản thực thi Stack Tuy nhiên, cách sử dụng nhiều Call Stack máy tính trường hợp độ sâu đệ quy lớn gây Stack Overflow Phát thành phần liên thông Một ứng dụng đơn giản để duyệt đồ thị phát thành phần liên thông Để phát thành phần liên thông đồ thị (vô hướng), ta thực lặp lại thao tác sau: chọn đỉnh chưa thăm thực thăm đỉnh thành phần liên thông chứa Thủ tục sau trả lại số thành phần liên thông đồ thị đầu vào ( , ) u GV E u GV E C C ( ( , )): mark all vertices unvisited ←0 for all vertices ∈ if is unvisited G T ( ( , ), ) algorithm]] ← +1 return count s s V GV E s [[any graph traversal count count count Code C: 10 11 12 ? int connected_component(){ memset(mark, UNVISITED, (n+1)*sizeof(int)); // mark all (# ve int s = 0; int count = 0; for(; s < n; s++){ if(mark[s] == UNVISITED){ bfs(s); count++; } } return count; } Code đầy đủ: list-representation (http://www.giaithuatlaptrinh.com/wp- content/uploads/2016/12/GraphBasics_List.c) , matrix-representation (http://www.giaithuatlaptrinh.com/wpcontent/uploads/2016/12/GraphBasics_Matrix.c) Tham khảo

Ngày đăng: 26/08/2022, 15:58

w