Giới thiệu thuật toán Floyd Warshall để tìm đường đi ngắn nhất.Thuật toán Floyd-Warshall là thuật toán tính đường ngắn nhất với mọi cặp đỉnh, tuân theo Quy hoạch động Dynamic Programming
Trang 1ĐẠI HỌC QUỐC GIA TP HCM TRƯỜNG ĐẠI HỌC BÁCH KHOA
BÀI TẬP LỚN MÔN HỌC ĐẠI SỐ TUYỂN TÍNH
ĐỀ TÀI 11 Giới thiệu thuật toán Floyd Warshall để tìm đường đi ngắn nhất, lập trình chương trình sử dụng thuật toán để tìm đường đi ngắn nhất, cho ví dụ về thuật
toán tương tự LỚP: L08 - NHÓM: 11, HK232 GVHD: HUỲNH THỊ VU SINH VIÊN THỰC HIỆN
7 NGÔ NGUYỄN NHẬT PHONG 2312620 100
Trang 2Thang đánh giá điểm % hoàn thành BTL
Trễ deadline nhóm đề ra mà không báo
nhóm trưởng gì hết (có báo không trừ
điểm)
trừ 5%
Không chịu chỉnh kiểu tài liệu tham khảo
cho đàng hoàng, nhờ nhóm trưởng làm
trừ 5%
Đạo văn hơn 50% mà không sửa lại trừ 20%
Làm sai, chưa hoàn thành tốt nhóm
trưởng kêu sửa, chỉ chi tiết luôn mà sửa
sơ sài, không hoàn thành đúng yêu cầu
trừ 30%
Làm sai, chưa hoàn thành tốt nhóm
trưởng kêu sửa thì bơ luôn trừ 50%
Nhóm trưởng phân việc mà không làm trừ 100%, báo cô, bạn rời nhóm
Khi ban quyết định kỷ luật nhóm trưởng sẽ báo cho thành viên trên nhóm 1 cách công khai, và nếu bạn thấy quyết định của nhóm trưởng chưa hợp lý thì có thể chất
vấn
Trang 3TP HỒ CHÍ MINH, NĂM 2023 BÁO CÁO KẾT QUẢ LÀM VIỆC NHÓM
1 LƯƠNG ANH KHOA 2011421 Nhóm trưởng Hoàn thành tốt
2 LÊ KIỀU MINH ANH 2110728 Giới thiệu thuật toán
Floyd Warshall để tìmđường đi ngắn nhất
Đã hoàn thànhbáo cáo + ppt
3 LÊ THỊ XUÂN MAI 2312017 Giới thiệu thuật toán
Dijkstra
Đã hoàn thànhbáo cáo + ppt
4 LÊ TIẾN PHÁT 2312579 Thuật toán Bellman-Ford Đã hoàn thành
báo cáo + ppt
5 LƯ CHẤN VŨ 2313955 Viết chương trình sử dụng
thuật toán trên để tìmđường đi ngắn nhất
Đã hoàn thànhbáo cáo + ppt
báo cáo + ppt
7 NGÔ NGUYỄN NHẬT
PHONG 2312620 Hierarchal RoutingAlgorithms Đã hoàn thànhbáo cáo + ppt
NHÓM TRƯỞNG (ghi rõ họ tên, ký tên)
Lương Anh Khoa
Trang 43 Giới thiệu các thuật toán khác để tìm đường đi ngắn nhất 14
Trang 51 Giới thiệu thuật toán Floyd Warshall để tìm đường đi ngắn nhất.
Thuật toán Floyd-Warshall là thuật toán tính đường ngắn nhất với mọi cặp đỉnh, tuân theo Quy hoạch động (Dynamic Programming) để kiểm tra mọi đường dẫn có thể
đi qua mọi điểm để tính toán khoảng cách ngắn nhất giữa mỗi cặp điểm Thuật toán dùng cho cả đồ thị chứa trọng số có hướng và không có hướng.
Cho đồ thị G với các đỉnh từ 1 tới N Tìm đường đi ngắn nhất từ đỉnh I đến đỉnh
J trong đồ thị G (shortestPathMatrix[i][j]).
Ý tưởng cho thuật toán này là dùng một điểm K làm điểm trung gian, điểm K này lần lượt là các đỉnh từ 1 đến N của đồ thị G để tìm đường ngắn nhất.
Mô tả thuật toán:
Bước 1: Tạo một ma trận đầu vào.
Bước 2: Xét một đỉnh là đỉnh trung gian (tạm gọi là K) và đã coi các đỉnh {0, 1,
2, k-1} là các đỉnh trung gian Chọn 2 đỉnh phân biệt (khác K) đặt lần lượt là I và J.
Bước 3: Với mọi cặp (I,J) trong đồ thị, có hai trường hợp xảy ra:
Trường hợp 1: K không là đỉnh trung gian trong đường ngắn nhất từ I tới J.
Trường hợp này giữ nguyên giá trị khoảng cách I đến J (dist[i][j])
Trang 6Trường hợp 2: K là đỉnh trung gian thuộc đường ngắn nhất từ I đến J Trường hợp này giá trị khoảng cách từ I đến J (dist[i][j]) phải ghi lại giá trị mới là kết quả của phép toán tổng của khoảng cách từ I đến K với khoảng cách từ K đến J: dist[i][k] + dist[k][j], if dist[i][j] > dist[i][k] + dist[k][j] Nếu đã có khoảng cách từ I đến J cho trước thì so sánh khoảng cách đã cho trước với giá trị vừa tính và ghi lại giá trị nhỏ hơn.
Bước 4: Lặp lại thuật toán cho toàn bộ cặp (I,J) trong ma trận.
1.2 Ví dụ 1
Đồ thị chứa trọng số có hướng
Cho đồ thị chứa trọng số có hướng:
Bước 1: Tạo một ma trận đầu vào Hai đỉnh cần tìm quy ước là i và j.
Quy ước:
Nếu đồ thị có trọng số khoảng cách từ i đến j thì đó là giá trị của phần tử hàng i cột j (Distance[i][j]) Vì trọng số trong đồ thị này có hướng nên khoảng cách từ i đến j khác với khoảng cách từ j đến i.
Nếu đồ thị không có trọng số khoảng cách từ i đến j thì giá trị (Distance[i][j]) đặt
là vô cực (∞).
Trang 7Ta được ma trận đầu vào như sau:
Bước 2: Chọn đỉnh A như một đỉnh trung gian và tính khoảng cách cho từng cặp
(i,j) (Distance[i][j]) bằng phép toán:
= Distance[i][j] = minimum (Distance[i][j], (Distance from i to A) + (Distance from A to j ))
= Distance[i][j] = minimum (Distance[i][j], Distance[i][A] + Distance[A][j]) Phép toán tính tổng của khoảng cách từ đỉnh i đến đỉnh A và khoảng cách từ đỉnh
A đến đỉnh j, sau đó so sánh với giá trị hiện tại của nó và ghi lại giá trị nhỏ hơn.
Ví dụ:
Khoảng cách từ C đến B là phần tử hàng C cột B được tính bằng phép toán:
Trang 8= Distance[C][B] = minimum (Distance[C][B], (Distance from C to A) +
Khoảng cách từ C đến D là phần tử hàng C cột D được tính bằng phép toán:
= Distance[C][D] = minimum (Distance[C][D], (Distance from C to A) +
Bước 3: Lặp lại tương tự lần lượt với các đỉnh B, C, D, E làm đỉnh trung gian.
Sau khi có trọng số cho các cặp đỉnh, tiến hành kiểm tra bằng cách lặp lại thuật toán một lần nữa.
Ta được ma trận đường đi ngắn nhất giữa các cặp đỉnh:
Trang 91.3 Ví dụ 2
Đồ thị chứa trọng số không có hướng.
Cho đồ thị chứa trọng số không có hướng như sau:
Bước 1: Tạo một ma trận đầu vào Hai đỉnh cần tìm quy ước là i và j.
Quy ước:
Nếu đồ thị có trọng số khoảng cách từ i đến j thì đó là giá trị của phần tử hàng i cột j (Distance[i][j]) Trong ví dụ này trọng số không có hướng nên khoảng cách từ i đến j cũng chính là khoảng cách từ j đến i.
Nếu đồ thị không có trọng số khoảng cách từ i đến j thì giá trị (Distance[i][j]) đặt
là vô cực (∞).
Ta được ma trận đầu vào như sau:
Trang 10Bước 2: Chọn đỉnh 1 như một đỉnh trung gian (K=1) và tính khoảng cách cho
từng cặp (i,j) (Distance[i][j]) bằng phép toán:
= Distance[i][j] = minimum (Distance[i][j], (Distance from i to 1) + (Distance from 1 to j ))
= Distance[i][j] = minimum (Distance[i][j], Distance[i][1] + Distance[1][j]) Phép toán tính tổng của khoảng cách từ đỉnh i đến đỉnh 1 và khoảng cách từ đỉnh
1 đến đỉnh j, sau đó so sánh với giá trị hiện tại của nó và ghi lại giá trị nhỏ hơn.
= Distance[2][4] = minimum (Distance[2][4], (Distance from 2 to 1) + (Distance from 1 to 4 ))
Chính là:
Trang 11= Distance[1][4] = minimum (Distance[1][4], (Distance from 1 to 5) + (Distance from 5 to 4 ))
Bước 3: Lặp lại tương tự lần lượt với các đỉnh 2, 3, 4, 5 làm đỉnh trung gian Sau
khi có trọng số cho các cặp đỉnh, tiến hành kiểm tra bằng cách lặp lại thuật toán một lần nữa.
Trang 12Ta được ma trận đường đi ngắn nhất giữa các cặp đỉnh:
Thực tế, có nhiều tuyến đường từ kí túc xá khu B-ĐHQG TP.HCM đến kí túc xá khu
A-ĐHQG TP.HCM nhưng trong ví dụ này chỉ nêu một vài tuyến đường để ma trận không quá lớn Ngoài ra, Google Maps còn dựa vào lưu lượng giao thông tại thời điểm truy cập và một số thuật toán khác để chọn đường đi ngắn nhất với thời gian ngắn nhất, vì vậy, ví dụ này chỉ xét về khoảng cách ngắn nhất từ điểm xuất phát đến điểm đích và không xét thời gian, mật độ lưu thông…
Giải:
Trang 13Bước 1: Dựa vào bản đồ lập ma trận vuông giữa các cặp điểm Ở ví dụ này là ma trận
vuông 10x10, gọi ma trận này là ma trận D.
Cho rằng: Các đoạn đường này đều là đường hai chiều Ta quy về bài toán trọng số không có hướng.
Tức là:
Khoảng cách từ điểm I đến điểm J chính là giá trị của phần tử dij.
Khoảng cách từ điểm I đến điểm J cũng chính là khoảng cách từ điểm J đến điểm I (dij = dji).
Khoảng cách giữa cặp điểm chưa có trọng số sẽ được ghi là vô cực (∞)
Trang 14Bước 2: Chọn đỉnh trung gian Tính khoảng cách giữa các cặp đỉnh và so sánh để tìm
Ví dụ: Khoảng cách từ điểm E đến điểm G là:
Distance[E][G] = Distance from E to KA + Distance from KA to G
dEG = dEKA + dKAG = 0.5 + 0.47 = 0.97
So sánh với giá trị đã có trong ma trận dEG trong ma trận là vô cực lớn hơn dEG vừa tính là 0.97 (∞ > 0.97) Vậy dEG ngắn nhất tạm thời được ghi lại là 0.97 qua A là đỉnh trung gian.
Tương tự lần lượt các đỉnh còn lại làm đỉnh trung gian.
Bước 3: Lặp lại thuật toán một lần nữa trên toàn bộ ma trận để kiểm tra đường đi ngắn
Trang 15#define n 5 //Kich thuoc cua ma tran do thi
#define INF 99999 //Khi do thi khong co trong so khoang cach tu dinh i den dinh j
void floydWarshall(int graph[][n]) { //Ham Floyd Warshall tim duong di ngan nhat giua cac dinh
Trang 16for (int j = 0; j < n; j ++ ) {
if (graph[i][j] == INF) cout << "INF ";
else cout << setw(3) << graph[i][j] << " ";
//Khoi tao do thi 1
int graph1[n][n] = { {0, 4, INF, 5, INF},
{INF, 0, 1, INF, 6}, {2, INF, 0, 3, INF}, {INF, INF, 1, 0, 2}, {1, INF, INF, 4, 0} };
cout << "Example1: " << endl;
print(graph1);
cout << "After run the Floyd Warshall algorithm: " << endl; floydWarshall(graph1);
print(graph1);
//Khoi tao do thi 2
int graph2[n][n] = { {0, 5, INF, 9, 1},
{5, 0, 2, INF, INF}, {INF, 2, 0, 7, INF}, {9, INF, 7, 0, 2}, {1, INF, INF, 2, 0} };
cout << "Example2: " << endl;
Giải thích code bằng ngôn ngữ C++:
● Bước 1: Khởi tạo ma trận với kích thước n x n.
Trang 17○ Khởi tạo mảng có kiểu dữ liệu int[n][n].
○ Nếu đồ thị có trọng số khoảng cách từ i đến j thì gán giá trị trọng số đó vào phần tử thứ [i][j].
○ Nếu đồ thị không có khoảng cách từ i đến j thì gán giá trị INF (vô cùng) vào phần tử thứ[i][j].
● Bước 2: Sử dụng 3 vòng lặp lồng nhau để duyệt qua tất cả các cặp đỉnh (i, j) và mọi đỉnh trung gian k.
○ Vòng lặp ngoài cùng duyệt qua từng đỉnh trung gian k.
○ Vòng lặp giữa duyệt qua từng đỉnh xuất phát i.
○ Vòng lặp trong cùng duyệt qua từng đỉnh đích j.
● Bước 3: Tính toán khoảng cách ngắn nhất từ đỉnh xuất phát đến đỉnh đích.
○ Nếu có đường đi từ i đến k và từ k đến j thì so sánh khoảng cách giữa i đến j hiện tại và tổng khoảng cách từ i đến k và từ k đến j.
○ Nếu tổng khoảng cách từ i đến k và từ k đến j nhỏ hơn khoảng cách từ i đến j hiện tại thì gán giá trị tổng đó vào phần tử thứ [i][j].
○ Xét ví dụ 1: khi k = 0, i = 2, j = 1 ta so sánh giữa tổng khoảng cách giữa phần tử thứ [i][k] và phần tử thứ [k][j] = 2 + 4 < khoảng cách của phần
tử thứ [i][j] hiện tại = INF, ta cập nhật giá trị mới phần tử thứ [i][j] thành 6.
○ Lặp lại tương tự cho đến khi cả k, i, j đạt giá trị n - 1, ta được ma trận đường đi ngắn nhất giữa các cặp đỉnh.
2.2 Kết quả ví dụ 1
Trang 182.3 Kết quả ví dụ 2
3 Giới thiệu các thuật toán khác để tìm đường đi ngắn nhất.
3.1 Thuật toán Dijkstra
3.1.1 Giới thiệu Dijkstra
Thuật toán Dijkstra là một thuật toán phổ biến để giải bài toán đường đi ngắn nhất một nguồn không có trọng số âm trong đồ thị, tức là tìm khoảng cách ngắn nhất giữa hai đỉnh trên đồ thị Nó được nhà khoa học máy tính người Hà Lan Edsger W Dijkstra nghĩ ra vào năm 1956.
Thuật toán duy trì một tập hợp các đỉnh đã thăm và một tập hợp các đỉnh chưa thăm Nó bắt đầu tại đỉnh nguồn và lặp đi lặp lại chọn đỉnh chưa được thăm với khoảng cách dự kiến nhỏ nhất tới nguồn Sau đó, nó sẽ thăm các đỉnh lân cận của đỉnh này và cập nhật khoảng cách dự kiến của chúng nếu tìm thấy đường đi ngắn hơn Quá trình này tiếp tục cho đến khi đạt đến đỉnh đích hoặc tất cả các đỉnh có thể tiếp cận đã được thăm.
Thuật toán Dijkstra có thể hoạt động trên cả đồ thị có hướng và đồ thị vô hướng:
• Trong đồ thị có hướng, mỗi cạnh có một hướng, biểu thị hướng di chuyển giữa các đỉnh được nối bởi cạnh đó Trong trường hợp này, thuật toán tuân theo hướng của các cạnh khi tìm kiếm đường đi ngắn nhất.
Trang 19• Trong đồ thị vô hướng, các cạnh không có hướng và thuật toán có thể kiểm tra
cả chiều thuận và chiều ngược của các cạnh khi tìm kiếm đường đi ngắn nhất.
3.1.2 Lý thuyết
Mô tả thuật toán Dijkstra:
1 Đánh dấu đỉnh nguồn có khoảng cách hiện tại là 0 và phần còn lại là vô cùng.
2 Đặt đỉnh không được thăm có khoảng cách hiện tại nhỏ nhất làm đỉnh hiện tại.
3 Đối với mỗi đỉnh lân cận [v], đỉnh hiện tại [u] sẽ cộng khoảng cách hiện tại của nó với trọng số của cạnh nối Nếu nhỏ hơn khoảng cách hiện tại của đỉnh [v], đặt
nó làm khoảng cách hiện tại mới (d[v] = min(d[v], d[u] + len(u,v)).
4 Đánh dấu đỉnh hiện tại là đã thăm.
5 Quay lại bước 3 nếu còn đỉnh chưa được thăm.
3.1.3 Ví dụ
Ví dụ 1: Đồ thị có hướng chứa trọng số
Thuật toán Dijkstra sẽ tạo ra đường đi ngắn nhất từ đỉnh 0 đến tất cả các đỉnh khác trong biểu đồ.
Hãy xem xét biểu đồ dưới đây:
Với biểu đồ này, giả sử rằng trọng số của các cạnh là khoảng cách giữa hai đỉnh.
Trang 20Ban đầu ta đặt như sau:
• Khoảng cách từ đỉnh nguồn đến chính nó là 0 Trong ví dụ này, đỉnh nguồn là 0.
• Khoảng cách từ đỉnh nguồn đến tất cả các đỉnh khác là không xác định nên đánh dấu tất cả chúng là vô cùng.
0 → 0, 1→ ∞, 2 → ∞, 3 → ∞, 4 → ∞, 5 → ∞, 6 → ∞.
• Thuật toán sẽ hoàn thành khi tất cả các đỉnh đã được thăm và khoảng cách giữa chúng được thêm vào đường dẫn.
Hiện tại có các đỉnh chưa được thăm: 0 1 2 3 4 5 6.
Bước 1: Bắt đầu từ đỉnh 0 và đánh dấu đỉnh là đã thăm.
Bước 2: Kiểm tra các đỉnh liền kề, chọn đỉnh 1 có khoảng cách là 2 hoặc chọn
đỉnh 2 có khoảng cách là 6 Chọn đỉnh có khoảng cách nhỏ nhất Trong bước này, đỉnh
1 là đỉnh liền kề với khoảng cách nhỏ nhất, vì vậy đánh dấu nó là đã thăm và cộng khoảng cách.
Khoảng cách: đỉnh 0 → đỉnh 1 = 0 + 2 = 2
Trang 21Bước 3: Kiểm tra đỉnh liền kề là đỉnh 3, đánh dấu nó là đã thăm và cộng khoảng
cách.
Khoảng cách: đỉnh 0 → đỉnh 1 → đỉnh 3 = 0 + 2 + 5 = 7
Bước 4: Kiểm tra các đỉnh liền kề, chọn đỉnh 4 có khoảng cách là 10 hoặc đỉnh 5
có khoảng cách là 15 Chọn đỉnh có khoảng cách nhỏ nhất Đỉnh 4 là đỉnh liền kề có khoảng cách nhỏ nhất, đánh dấu nó là đã thăm và cộng khoảng cách.
Khoảng cách: đỉnh 0 → đỉnh 1 → đỉnh 3 → đỉnh 4 = 0 + 2 + 5 + 10 = 17
Trang 22Bước 5: Kiểm tra đỉnh liền kề là đỉnh 6, đánh dấu nó là đã thăm và cộng khoảng
Trang 233 x x 6 7 ∞ ∞ ∞
Vậy, khoảng cách ngắn nhất từ đỉnh nguồn 0 đến đỉnh đích 6 là 19.
Ví dụ 2: Đồ thị vô hướng chứa trọng số.
Cho đồ thị như sau:
Ban đầu ta đặt như sau:
• Khoảng cách từ đỉnh nguồn đến chính nó là 0 Trong ví dụ này, đỉnh
Trang 24Bước 1: Bắt đầu từ đỉnh 1 và đánh dấu đỉnh là đã thăm.
Bước 2: Kiểm tra các đỉnh liền kề, chọn đỉnh 2 có khoảng cách là 7 hoặc chọn
Trang 25Bước 5: Kiểm tra đỉnh liền kề là đỉnh 5, đánh dấu nó là đã thăm và cộng khoảng
Trang 263 x x 9 16 ∞ ∞
Vậy, khoảng cách ngắn nhất từ đỉnh nguồn 1 đến đỉnh đích 6 là 17.
3.2 Thuật toán Bellman-Ford
Mặc dù Bellman-Ford chậm hơn thuật toán của Dijkstra nhưng nó có khả năng
xử lý các đồ thị có trọng số cạnh âm, khiến nó trở nên linh hoạt hơn, lặp lại chu trình
để cập nhật khoảng cách ngắn nhất từ đỉnh nguồn đến các đỉnh còn lại, đây là một tính năng.
Thuật toán Bellman-Ford được phát biểu như sau: Khởi tạo các mảng khoảng cách d[s]:=0 và d[v]:=+∞, v≠s, sau đó thực hiện phép co theo mọi cạnh của đồ thị Cứ lặp lại như vậy cho đến khi không thể âm thêm bất k một nhãn d[v] nào nữa Vì vậy có thể nói Bellman-Ford là dựa vào “nguyên tắc xét lại” (principle of relaxation).
Mô tả thuật toán:
· Khởi tạo mảng khoảng cách dist[] cho mỗi đỉnh 'v' dưới dạng dist[v] =
∞(INFINITY).
· Giả sử đỉnh ‘0’ làm nguồn và gán dist = 0
Trang 27· Duyệt tất cả các edge (u,v,weight) N-1 lần theo điều kiện dưới đây:
· dist[v] = minimum(dist[v], distance[u] + weight)
· Bây giờ, xét tất cả các cạnh một lần nữa, tức là lần thứ N và dựa trên hai trường hợp dưới đây, chúng ta có thể phát hiện chu kỳ âm:
· Trường hợp 1 ( xuất hiện chu trình âm): Với tất cả edge(u, v, weight), if dist[u] + weight < dist[v].
· Trường hợp 2 (không xuất hiện chu trình âm): trường hợp 1 không cần xét.
3.2.2 Ví dụ
Ví dụ về khảo sát sự xuất hiện của chu trình âm
Giả sử chúng ta có một biểu đồ dưới đây và chúng ta muốn tìm xem liệu có tồn tại một chu trình âm hay không bằng cách sử dụng Bellman-Ford.
Bước 1: Khởi tạo mảng khoảng cách Dist[] để lưu trữ khoảng cách ngắn nhất cho mỗi đỉnh từ đỉnh nguồn Ban đầu khoảng cách của nguồn sẽ là 0 và khoảng cách của các đỉnh khác sẽ là ∞ (INFINITY).