1.1.3 Đồ thị có trọng số Với các đồ thị như trình bày ở trên, mỗi cạnh của đồ thị chỉ biểu thị rằng có một liên kết nào đó từ đỉnh này tới đỉnh khác của đồ thị.. Để biểu diễn đồ thị có t
Trang 1CHUYÊN ĐỀ CÔNG NGHỆ PHẦN MỀM
1 Họ và tên Sinh viên
- Nguyễn Văn Tiến
- Mai Văn Tú
- Nguyễn Văn Ninh
2 Tên đề tài: TÌM HIỂU CÁC THUẬT TOÁN TRÊN ĐỒ THỊ
3 Giới thiệu đề tài
Đồ thị là một lĩnh vực đã có từ lâu và có nhiều ứng dụng hiện đại.
Đồ thị được sử dụng để giải các bài toán trong nhiều lĩnh vực khác nhau.
Đồ thị có trọng số trên các cạnh có thể sử dụng để giải các bài toán như: Tìm đường đi ngắn nhất giữa hai thành phố trong mạng giao thông Chúng ta cũng còn sử dụng đồ thị để giải các bài toán về lập lịch, thời khóa biểu, và phân bố tần số cho các trạm phát thanh và truyền hình…
Đồ thị là một cấu trúc rời rạc bao gồm các đỉnh và các cạnh nối các đỉnh này Chúng ta phân biệt các loại đồ thị khác nhau bởi kiểu và số lượng cạnh nối hai đỉnh nào đó của đồ thị
4 Các nội dung chính
- Tổng quan
- Thuật toán tìm kiếm
- Thuật toán tìm đường đi ngắn nhất
- Cây bao trùm nhỏ nhất
Trang 2Chương 1 Tổng Quan
1.1 Các khái niệm cơ bản
1.1.1 Đồ thị vô hướng
Đồ thị có hướng G=<V,E> bao gồm:
- V là tập hợp hữu hạn các đỉnh
- E là một tập hợp hữu hạn, có thứ tự các cặp đỉnh V, gọi là các cạnh
Ví dụ:
Đồ thị có hướng G1=<V1,E1>
V1={a, b, c, d}; E1={(a, b); (a, c); (b,d); (c, b); (d,d)}
Biểu diễn hình học của đồ thị này như sau:
Trong đồ thị có hướng, cạnh là một cặp có thứ tự các đỉnh, vi vậy cạnh (a, c) và (c, a) là khác nhau Ngoài ra một đỉnh cũng có thể nối đến chính nó để tạo thành một cạnh
Mỗi thành phần thuộc V được gọi là một đỉnh hoặc một nút của đồ thị vì vậy V được gọi là tập các đỉnh của đồ thị Mỗi thành phần thuộc E gọi là một cạnh hoặc một cung , vì vậy E gọi là tập các cạnh của đồ thị
Một cạnh (u, v) của đồ thị có hướng có thể được biểu thị dạnh u v Đỉnh u khi
đó được gọi là đỉnh kề của v Cạnh (u, v) được gọi là cạnh xuất phát từ u Ta kí hiệu A(u) là tập các cạnh xuất phát từ u Cạnh (u, v) cũng được gọi là cạnh đi tới v, kí hiệu I(v) là tập các cạnh đi tới v
Bậc ngoài của 1 đỉnh là số các cạnh xuất phát từ đỉnh đó Do đó bậc ngoài của u=|A(u)|.Bậc trong của một đỉnh là số các cạnh đinh tới đỉnh đó, do đó bậc trong của v=| I(v)|
1.1.2 Đồ thị vô hướng
Đồ thị vô hướng là đồ thị có các cạnh không có hướng Hai nút ở hai đầu của cạnh có vai trò như nhau
Trang 3Đồ thị vô hướng G=(V,E) bao gồm:
- V là một tập hợp hữu hạn các đỉnh
- E là tập hợp hữu hạn các cặp đỉnh phân biệt của V, gọi là các cạnh
Ví dụ:
Đồ thị vô hướng G2=<V2,E2>
V2={a, b, c, d}; E1={(a, b); (a, c); (b,d); (c, b); }
Khi đó biểu diễn hình học của đồ thị nay như sau:
Các cạnh của đồ thị là không có hướng, do vậy cạnh (u,v)~ cạnh (v,u) Trong đồ thị vô hướng cạnh (u,v) được coi là cạnh xuất phát đồng thời là cạnh đi tới u hoặc v Bậc của một đỉnh là tổng số cạnh xuất phát cũng như đi tới đỉnh đó
1.1.3 Đồ thị có trọng số
Với các đồ thị như trình bày ở trên, mỗi cạnh của đồ thị chỉ biểu thị rằng có một liên kết nào đó từ đỉnh này tới đỉnh khác của đồ thị Tuy nhiên, trong thực tế có rất nhiều ứng dụng của đồ thị cần thêm một số thông tin cho liên kết này Chẳng hạn khoảng cách giữa 2 nút của đồ thị là 2 thành phố trên bản đồ, đồ thị biểu thị việc chuyển trạng thái của một loại máy dưới tác động của một số thao tác, v.v
Đồ thị có trọng số
Ninh Bình
Thai Bình
40Km
155Km
96Km
45Km
Trang 41.2 Biểu diễn đồ thị
1.2.1 Biểu diễn đồ thị bằng ma trận kề
Giả sử ta có một đồ thị có hướng G = <V, E> bao gồm n đỉnh {v1, v2, vn} Phương pháp biểu diễn đồ thị bằng ma trận kề sử dụng 1 ma trận A (n x n) được xác định như sau:
1 nếu (vi, vj) E Aij =
0 nếu (vi, vj)∉E
Có nghĩa là phần tử ở hàng i, cột j của ma trận A có giá trị 1 khi có một cạnh nối từ vi đến vj Ngược lại, phần tử đó có giá trị 0
Ma trận trên được gọi là ma trận kề của đồ thị có hướng (V, E)
Để biểu diễn đồ thị có trọng số bằng ma trận kề, ta thay các phần tử có giá trị 1 trong ma trận bằng chính trọng số của cạnh tương ứng, và với các phần tử có giá trị 0, ta thay bằng 1 giá trị cho biết không có cạnh nối 2 đỉnh tương ứng
Ưu điểm của phương pháp biểu diễn đồ thị bằng ma trận kề là ta có thể dễ dàng biết được có một cạnh nối 2 đỉnh vi, vj hay không và có trọng số bao nhiêu Tuy nhiên, phương pháp này có nhược điểm là kích thước ma trận kề luôn luôn là n x n bất kể đồ thị có bao nhiêu cạnh
1.2.2 Biểu diễn đồ thị bằng danh sách cạnh
Phương pháp này sử dụng một danh sách liên kết cho mỗi đỉnh của đồ thị Danh sách liên kết của một đỉnh sẽ chứa các đỉnh khác kề với nó, do vậy danh sách này gọi
là danh sách kề
Ví dụ, với đồ thị có hướng
Với đồ thị vô hướng
Trang 5Chương 2: Thuật toán tìm kiếm trên đồ thị
Trong khi giải bài toán được mô tả bằng đồ thị, ta cần đi qua các đỉnh và các cung của đồ thị Việc đi qua các đỉnh của đồ thị như vậy gọi là thuật toán tìm kiếm hay còn gọi là duyệt đồ thị
2.1 Thuật toán tìm kiếm theo chiều sâu DFS.
Quá trình duyệt theo chiều sâu bắt đầu từ một đỉnh nào đó của đồ thị Sau khi thăm đỉnh này, quá trình duyệt theo chiều sâu được lặp lại với tất cả các đỉnh kề của nó Tuy nhiên đồ thị có thể tồn tại chu trình, do vậy ta cần phải đánh dấu các đỉnh đã duyệt để tránh duyệt lại đỉnh này một lần nữa
Với trình tự duyệt như trên quá trình duyệt sẽ duyệt hết một nhánh một nhánh của
đồ thị rồi mới sang nhánh khác
Ví dụ:
Quá trình duyệt theo chiều sâu bắt đầu từ đỉnh a, thứ tự duyệt như sau:
- Sau khi thăm đỉnh a, tiến hành thăm đỉnh kề với a là b tiếp theo thăm đỉnh kề với b là d Đỉnh d không kề với đỉnh nào, do vậy quay lại bước trước
- Đỉnh b chỉ có một đỉnh kề là d, đã thăm, do vậy quay lại bước trước
- Đỉnh a còn đỉnh kề là c chưa thăm, do vậy tiến hành thăm đỉnh này
Như vậy, thứ tự các đỉnh trong quá trình duyệt sẽ là: a, b, d, c
Quá trình duyệt sẽ chỉ duyệt theo các cạnh dẫn tới các đỉnh chưa thăm Các cạnh dẫn tới các đỉnh thăm rồi sẽ được bỏ qua Chẳng hạn trong quá trình duyệt đồ thị trên khi duyệt đến đỉnh c cạnh nối tới b sẽ được bỏ qua vì đỉnh b được thăm rồi
Trang 62.2 Thuật toán tìm kiếm theo chiều rộng BFS.
Quá trình duyệt theo chiều rộng bắt đầu từ một đỉnh nào đó của đồ thị, tiếp đến các đỉnh kề của nó sẽ được thăm, rồi tiếp tục đến các đỉnh kề của các đỉnh vừa thăm v.v
Như vậy, quá trình duyệt theo chiều rộng không duyệt theo từng nhánh của đồ thị mà duyệt theo độ sâu của các đỉnh so với đỉnh ban đầu Từ đỉnh bắt đầu, các đỉnh
có khoảng cánh với đỉnh ban đầu là 1 được duyệt, tiếp đến là các đỉnh có khoảng cách
2 v.v
Ví dụ:
Quá trình duyệt theo chiều rộng:
- Bắt đầu từ đỉnh a
- Sau khi thăm đỉnh a, tiến hành thăm đỉnh kề với a là b và c
- Tiếp theo thăm đỉnh kề với b là d
- Đỉnh kề với c là b đã được thăm rồi nên bỏ qua
Như vậy, thứ tự các đỉnh được thăm là a, b, c,d
Chương 3: Thuật toán tìm đường đi ngắn nhất
Trong thực tế, mạng lưới giao thông đường bộ, đường thủy hoặc đường hàng không Người ta không chỉ quan tâm đến việc tìm đường đi giữa hai địa điểm mà còn phải lựa chọn một hành trình tiết kiệm nhất về không gian, thời gian cũng như là chi phí Khi đó phát sinh yêu cầu tìm đường đi ngắn nhất giữa hai đỉnh của đồ thị Bài toán
đó phá biểu dưới dạng tổng quát như sau: Cho đồ thị có trọng số G=(V,E), hãy tìm một đường đi ngắn nhất từ điểm xuất phát S đến đích F Độ dài của đường đi này ta sẽ ký hiệu là d[S,F] Nếu như không tồn tại đường đi từ S tới F thì ta sẽ đặt khoảng cách đó
=+∞
• Nếu như đồ thị có chu trình âm (chu trình với độ dài âm) thì khoảng cách giữa một số cặp đỉnh nào đó có thể không xác định, bởi vì bằng cách đi vòng theo chu trình này một
số lần đủ lớn, ta có thể chỉ ra đường đi giữa hai đỉnh nào đó trong chu trình này nhỏ hơn bất kỳ một số cho trước nào Trong trường hợp như vậy, có thể đặt vấn đề tìm đường đi cơ bản (đường đi không có đỉnh lặp lại) ngắn nhất Vấn đề đó là một vấn đề hết sức phức tạp mà ta sẽ không bàn tới ở đây
• Nếu như đồ thị không có chu trình âm thì ta có thể chứng minh được rằng một trong những đường đi ngắn nhất là đường đi cơ bản Và nếu như biết được khoảng cách từ
Trang 7S tới tất cả những đỉnh khác thì đường đi ngắn nhất từ S tới F có thể tìm được một cách dễ dàng qua thuật toán sau:
Gọi c[u, v] là trọng số của cạnh [u, v] Qui ước c[v, v] = 0 với mọi v ∈ V và c[u, v] = +∞ nếu như (u, v) ∉ E Đặt d[S, v] là khoảng cách từ S tới v Để tìm đường đi từ S tới F, ta
có thể nhận thấy rằng luôn tồn tại đỉnh F1 ≠ F sao cho:
d[S, F] = d[S, F1] + c[F1, F]
(Độ dài đường đi ngắn nhất S->F = Độ dài đường đi ngắn nhất S->F1 + Chi phí đi từ F1 tới F)
Đỉnh F1 đó là đỉnh liền trước F trong đường đi ngắn nhất từ S tới F Nếu F1≡S thì đường đi ngắn nhất là đường đi trực tiếp theo cung (S, F) Nếu không thì vấn đề trở thành tìm đường đi ngắn nhất từ S tới F1 Và ta lại tìm được một đỉnh F2 khác F và F1 để:
d[S, F1] = d[S, F2] + c[F2, F1]
3.1 Thuật toán FORD BELLMAN
Thuật toán Ford-Bellman có thể phát biểu rất đơn giản:
Với đỉnh xuất phát S Gọi d[v] là khoảng cách từ S tới v
Ban đầu d[v] được khởi gán bằng c[S, v]
Sau đó ta tối ưu hoá dần các d[v] như sau: Xét mọi cặp đỉnh u, v của đồ thị, nếu có một cặp đỉnh u, v mà d[v] > d[u]+ c[u, v] thì ta đặt lại d[v] := d[u] + c[u, v] Tức là nếu độ dài đường đi từ S tới v lại lớn hơn tổng độ dài đường đi từ S tới u cộng với chi phí đi từ u tới v thì ta sẽ huỷ bỏ đường đi từ S tới v đang có và coi đường đi từ S tới v chính là đường đi từ S tới u sau đó đi tiếp từ u tới v
Chú ý rằng ta đặt c[u, v] = +∞ nếu (u, v) không là cung Thuật toán sẽ kết thúc khi không thể tối ưu thêm bất kỳ một nhãn d[v] nào nữa
Tính dúng của thuật toán:
• Tại bước lặp 1: Bước khởi tạo d[v] = c[S, v]: thì dãy d[v] chính là độ dài ngắn nhất của đường đi từ S tới v qua không quá 1 cạnh
• Giả sử tại bước lặp thứ i (i ≥ 1), d[v] bằng độ dài đường đi ngắn nhất từ S tới v qua không quá I cạnh, thì do tính chất: đường đi từ S tới v qua không quá i + 1 cạnh sẽ phải thành lập bằng cách:
lấy một đường đi từ S tới một đỉnh u nào đó qua không quá i cạnh, rồi đi tiếp tới v bằng cung (u, v) Nên độ dài đường đi ngắn nhất từ S tới v qua không quá i + 1 cạnh sẽ được tính bằng giá trị nhỏ nhất trong các giá trị: (Nguyên lý tối ưu Bellman)
♦ Độ dài đường đi ngắn nhất từ S tới v qua không quá i cạnh
♦ Độ dài đường đi ngắn nhất từ S tới u qua không quá i cạnh cộng với trọng số cạnh (u, v) (∀u)
Vì vậy, sau bước lặp tối ưu các d[v] bằng công thức
Trang 8d[v]bước i+1 = min(d[v]bước i, d[u]bước i+ c[u, v]) (∀u)
thì các d[v] sẽ bằng độ dài đường đi ngắn nhất từ S tới v qua không quá i + 1 cạnh Sau bước lặp tối ưu thứ n - 2, ta có d[v] = độ dài đường đi ngắn nhất từ S tới v qua không quá n – 1 cạnh Vì đồ thị không có chu trình âm nên sẽ có một đường đi ngắn nhất từ S tới v là đường đi cơ bản (qua không quá n - 1 cạnh) Tức là d[v] sẽ là độ dài đường đi ngắn nhất từ S tới v
Vậy thì số bước lặp tối ưu hoá sẽ không quá n - 2 bước
Trong khi cài đặt chương trình, nếu mỗi bước ta mô tả dưới dạng:
for u := 1 to n do
for v := 1 to n do
d[v] := min(d[v], d[u] + c[u, v]);
Thì do sự tối ưu bắc cầu (dùng d[u] tối ưu d[v] rồi lại có thể dùng d[v] tối ưu d[w] nữa ) nên chỉ làm tốc độ tối ưu nhãn d[v] tăng nhanh lên chứ không thể giảm đi được
3.2 Thuật toán DIJKSTRA
Trong trường hợp trọng số trên các cung không âm, thuật toán do Dijkstra đề xuất dưới đây hoạt động hiệu quả hơn nhiều so với thuật toán Ford-Bellman Ta hãy xem trong trường hợp này, thuật toán Ford-Bellman thiếu hiệu quả ở chỗ nào:
Với đỉnh v ∈ V, Gọi d[v] là độ dài đường đi ngắn nhất từ S tới v Thuật toán Ford-Bellman khởi tạo d[v] = c[S, v] Sau đó tối ưu hoá dần các nhãn d[v] bằng cách sửa nhãn theo công thức: d[v] := min(d[v], d[u] + c[u, v]) với ∀u, v ∈ V Như vậy nếu như ta dùng đỉnh u sửa nhãn đỉnh v, sau đó nếu ta lại tối ưu được d[u] thêm nữa thì ta cũng phải sửa lại nhãn d[v] dẫn tới việc d[v] có thể phải chỉnh đi chỉnh lại rất nhiều lần Vậy nên chăng, tại mỗi bước không phải ta xét mọi cặp đỉnh (u, v) để dùng đỉnh u sửa nhãn đỉnh v mà sẽ chọn đỉnh u là đỉnh mà không thể tối ưu nhãn d[u] thêm được nữa
Thuật toán Dijkstra (E.Dijkstra - 1959) có thể mô tả như sau:
Bước 1: Khởi tạo
Với đỉnh v ∈ V, gọi nhãn d[v] là độ dài đường đi ngắn nhất từ S tới v Ta sẽ tính các d[v] Ban đầu d[v] được khởi gán bằng c[S, v] Nhãn của mỗi đỉnh có hai trạng thái tự
do hay cố định, nhãn tự do có nghĩa là có thể còn tối ưu hơn được nữa và nhãn cố định tức là d[v] đã bằng độ dài đường đi ngắn nhất từ S tới v nên không thể tối ưu thêm Để làm điều này ta có thể sử dụng kỹ thuật đánh dấu: Free[v] = TRUE hay FALSE tuỳ theo d[v] tự do hay cố định Ban đầu các nhãn đều tự do
Bước 2: Lặp
Bước lặp gồm có hai thao tác:
1 Cố định nhãn: Chọn trong các đỉnh có nhãn tự do, lấy ra đỉnh u là đỉnh có d[u] nhỏ nhất, và cố định nhãn đỉnh u
2 Sửa nhãn: Dùng đỉnh u, xét tất cả những đỉnh v và sửa lại các d[v] theo công thức:
Trang 9d[v] := min(d[v], d[u] + c[u, v])
Bước lặp sẽ kết thúc khi mà đỉnh đích F được cố định nhãn (tìm được đường đi ngắn nhất từ S tới F); hoặc tại thao tác cố định nhãn, tất cả các đỉnh tự do đều có nhãn là +∞ (không tồn tại đường đi)
Có thể đặt câu hỏi, ở thao tác 1, tại sao đỉnh u như vậy được cố định nhãn, giả sử d[u] còn có thể tối ưu thêm được nữa thì tất phải có một đỉnh t mang nhãn tự do sao cho d[u] > d[t] + c[t, u] Do trọng số c[t, u] không âm nên d[u] > d[t], trái với cách chọn d[u] là nhỏ nhất Tất nhiên trong lần lặp đầu tiên thì S là đỉnh được cố định nhãn do d[S] = 0 Bước 3: Kết hợp với việc lưu vết đường đi trên từng bước sửa nhãn, thông báo đường
đi ngắn nhất
tìm được hoặc cho biết không tồn tại đường đi (d[F] = +∞)
Chương 4: Cây bao trùm ngắn nhất
4.1 Thuật toán Prim
4.1.1 Phương pháp
Bắt đầu tại một đỉnh A bất kì của đồ thị G gồm n đỉnh, ta tìm một đỉnh gần đỉnh A nhất trong số các đỉnh có đường đi từ đỉnh A, giả sử đỉnh đó là đỉnh B
Từ những đỉnh có đường đi từ đỉnh A và từ đỉnh B, ta lại chọn một đỉnh gần đỉnh
A hoặc gần đỉnh B nhất sao cho không tạo nên chu trình, giả sử đỉnh tìm được là đỉnh C
Quá trình tiếp tục như vậy cho đến khi không tim được đỉnh nào thỏa mãn Kết quả thu được là cây khung nhỏ nhất có n đỉnh và n-1 cạnh
4.1.2 Ví dụ minh họa
Trang 10Bắt đầu từ đỉnh 1.
Khoảng cách
d(1/2)=2 *<min>
d(1/3)=9
d(1/5)=15 Chọn 2, tập S={1, 2}
Tìm đỉnh gần đỉnh 1 hoặc đỉnh 2 nhất
d(1/3)=9 <min>
d(1/5)=15
d(2/4)=9
d(2/5)=15 Chọn đỉnh 3, tập S={1, 2, 3}
Tìm đỉnh gần đỉnh 1 hoặc đỉnh 2 hoặc đỉnh 3 nhất
d(1/5)=15
d(2/4)=9 <min>
d(2/5)=15
d(3/6)=15 Chọn đỉnh 4, tập S={1,2,3,4}
Quá trình tiếp tục như vậy
Kết quả:
Tập S={1,2,3,4,5,7,9,10,6,8}
Trang 114.2 Thuật toán Kruskal
4.2.1 Phương pháp
Cho đồ thị G=(X, E) Là một đò thị liên thông có trọng số gồm n đỉnh
Bước 1: Sắp xếp các cạnh theo thứ tự trọng số tang dần và khởi tạo T:= Ø
Bước 2: Lấy cạnh e ở đầu danh sách đã sắp xếp (có trọng số nhỏ nhất) Nếu T + {e} không chứa chu trình thì gán T:= T + {e} Loại cạnh e khỏi danh sách
Bước 3: Nếu T đủ n-1 phần tử thì dừng lại, ngược lại làm tiếp tục bước 2
4.2.2 Ví dụ minh họa
Cho đồ thị sau:
Tìm cây khung nhỏ nhất của đồ thị
Bước 1: Sắp xếp các cạnh theo thứ tự trọng số tăng dần và khởi tạo T := Ø
Gọi lstEdge là tập các cạnh đã sắp thứ tự trọng tăng: lstEdge = {(2,4); (1,2); (1,4); (3,4); (2,3)}
T := Ø
Bước 2: Lấy cạnh e ở đầu danh sách đã sắp xếp (có trọng nhỏ nhất) Nếu T + {e} không chứa chu trình thì gán T:= T + {e} Loại cạnh e khỏi danh sách
Vì cạnh (2,4) bổ sung vào T (T = Ø ) không tạo thành chu trình nên: T = {(2,4)}
Danh sách cạnh: {(1,2); (1,4); (3,4); (2,3)}
Bước 3: Nếu T đủ n-1 phần tử thì dừng, ngược lại làm tiếp tục bước 2
Ở đâyT chỉ có 1 phần tử < n – 1 = 4 – 1 = 3 nên thuật toán chưa dừng