1. Trang chủ
  2. » Giáo Dục - Đào Tạo

Đồ Án lập trình tính toán Đề tài 107 bài toán trên Đồ thị

34 3 0
Tài liệu đã được kiểm tra trùng lặp

Đ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

Tiêu đề Bài Toán Trên Đồ Thị
Tác giả Phạm Phú Thạnh, Dương Minh Hữu
Người hướng dẫn PGS. TS. Nguyễn Văn Hiệu
Trường học Trường Đại Học Bách Khoa
Chuyên ngành Công Nghệ Thông Tin
Thể loại Đồ án lập trình tính toán
Năm xuất bản 2024
Thành phố Đà Nẵng
Định dạng
Số trang 34
Dung lượng 1,84 MB

Nội dung

- Ứng dụng trong bản đồ số: Các ứng dụng bản đồ và định vị sử dụng thuật toán tìm đường đi ngắn nhất để chỉ đường và cung cấp các lộ trình tối ưu cho người dùng.. - Thuật toán Dijkstra:

Trang 1

TRƯỜNG ĐẠI HỌC BÁCH KHOA

KHOA CÔNG NGHỆ THÔNG TIN

ĐỒ ÁN LẬP TRÌNH TÍNH TOÁN

ĐỀ TÀI 107: BÀI TOÁN TRÊN ĐỒ THỊ

Người hướng dẫn: PGS TS NGUYỄN VĂN HIỆU

Sinh viên thực hiện:

Dương Minh Hữu LỚP: 23T_Nhat2 MÃ SV: 102230073

Đà Nẵng, 03/2024

Trang 2

6 TÀI LIỆU THAM KHẢO 24

7 PHỤ LỤC 25

Trang 3

1

MỞ ĐẦU

Thuật toán tìm đường đi ngắn nhất nhằm mục đích xác định con đường hoặc tuyến đường ngắn nhất giữa hai điểm trong một không gian hoặc mạng lưới Các thuật toán này được sử dụng rộng rãi trong nhiều lĩnh vực, bao gồm công nghệ, giao thông vận tải, logistics, viễn thông và nhiều ứng dụng khác Một số mục đích cụ thể của thuật toán tìm đường đi ngắn nhất bao gồm:

- Tối ưu hóa đường đi: Xác định đường đi hiệu quả nhất giữa hai điểm, giúp

tiết kiệm thời gian, chi phí hoặc tài nguyên

- Lập kế hoạch và điều phối: Trong ngành logistics và vận tải, thuật toán tìm

đường đi ngắn nhất giúp lập kế hoạch vận chuyển hiệu quả, tối ưu hóa việc giao hàng và phân phối

- Ứng dụng trong bản đồ số: Các ứng dụng bản đồ và định vị sử dụng thuật

toán tìm đường đi ngắn nhất để chỉ đường và cung cấp các lộ trình tối ưu cho người dùng

- Mạng máy tính và viễn thông: Thuật toán tìm đường đi ngắn nhất giúp định

tuyến dữ liệu hiệu quả trong mạng máy tính và hệ thống viễn thông, đảm bảo thông tin được truyền qua các kênh tốt nhất

- Quản lý giao thông: Các cơ quan giao thông sử dụng thuật toán này để tối

ưu hóa luồng giao thông, giảm tắc nghẽn và nâng cao an toàn

- Hỗ trợ ra quyết định: Thuật toán này cũng có thể được sử dụng để hỗ trợ

trong việc ra quyết định trong nhiều lĩnh vực khác nhau, nơi cần lựa chọn phương

án tốt nhất giữa nhiều lựa chọn

Trang 4

- Thuật toán Dijkstra: Đây là một trong những thuật toán phổ biến nhất để

giải bài toán tìm đường đi ngắn nhất trên đồ thị Thuật toán Dijkstra thường được sử dụng để tìm đường đi ngắn nhất từ một đỉnh đến tất cả các đỉnh còn lại trên đồ thị, hoặc từ một đỉnh đến một đỉnh cụ thể

- Thuật toán Floyd-Warshall: Thuật toán này được sử dụng để tìm tất cả các

đường đi ngắn nhất giữa tất cả các cặp đỉnh trên đồ thị, không chỉ tìm một đường đi ngắn nhất giữa hai đỉnh như Dijkstra hay Bellman-Ford

2.2 Cơ sở lý thuyết

Thuật toán Dijkstra tìm đường đi ngắn nhất từ một đỉnh nguồn đến tất cả các đỉnh khác trong một đồ thị có trọng số không âm Cơ sở lý thuyết của thuật toán này dựa trên:

• Greedy Algorithm (Thuật toán tham lam): Dijkstra hoạt động theo

nguyên tắc tham lam, nghĩa là nó chọn đường đi tốt nhất (ngắn nhất) có sẵn tại mỗi bước, dựa trên các thông tin đã biết

• Relaxation (Giãn cạnh): Khái niệm giãn cạnh (relaxation) là khi thuật

toán cố gắng tìm đường đi ngắn hơn đến một đỉnh thông qua một đỉnh trung gian Nếu nó tìm thấy một đường đi ngắn hơn, nó sẽ cập nhật giá trị khoảng cách đến đỉnh đó

• Min-Heap (Đống tối thiểu): Để chọn cạnh hoặc đỉnh với trọng số nhỏ

nhất một cách hiệu quả, Dijkstra thường sử dụng cấu trúc dữ liệu heap (thường là đống tối thiểu) Heap giúp tăng hiệu suất khi truy cập và cập nhật các giá trị trong quá trình tìm kiếm

Thuật toán Floyd-Warshall tìm đường đi ngắn nhất giữa tất cả các cặp đỉnh trong một đồ thị có trọng số (có thể có trọng số âm, nhưng không có chu trình âm) Cơ sở lý thuyết của thuật toán này dựa trên:

Trang 5

• Dynamic Programming (Lập trình động): Floyd-Warshall là một thuật

toán dựa trên lập trình động, nghĩa là nó giải quyết bài toán lớn hơn bằng cách chia nó thành các bài toán con nhỏ hơn và sử dụng kết quả của các bài toán con

để giải quyết bài toán lớn hơn

• Transitive Closure (Tính chất chuyển tiếp): Thuật toán dựa trên ý

tưởng rằng nếu bạn có đường đi ngắn nhất từ đỉnh i đến đỉnh k, và từ đỉnh k đến đỉnh j, thì bạn có thể tìm đường đi ngắn nhất từ i đến j bằng cách nối chúng lại Floyd-Warshall sử dụng nguyên lý này để liên tục cập nhật ma trận khoảng cách giữa các cặp đỉnh

• Triple Nested Loops (Vòng lặp lồng nhau ba lần): Thuật toán này thực

hiện ba vòng lặp lồng nhau để kiểm tra tất cả các cặp đỉnh i, j, và đỉnh trung gian

k Nếu đường đi qua đỉnh trung gian k ngắn hơn đường đi trực tiếp từ i đến j, nó

sẽ cập nhật ma trận khoảng cách

3 TỔ CHỨC CẤU TRÚC DỮ LIỆU VÀ THUẬT TOÁN

3.1 Phát biểu bài toán

+ Một đồ thị có hướng hoặc vô hướng với các cạnh có thể có trọng số âm

(nhưng không có chu trình âm) Đồ thị thường được biểu diễn dưới dạng ma trận kề

+ Ma trận kề ban đầu có thể sử dụng inf (vô cùng) cho các cặp đỉnh không kết nối trực tiếp, và 0 cho các đường từ một đỉnh đến chính nó

3.2 Cấu trúc dữ liệu

Thuật toán Dijkstra thường sử dụng các cấu trúc dữ liệu sau:

+ Danh sách kề (Adjacency List): Một danh sách kề lưu trữ các cạnh của đồ

thị theo cách mà mỗi đỉnh có một danh sách các cạnh liên kết với nó Mỗi cạnh

Trang 6

thường được biểu diễn dưới dạng một cặp (đỉnh, trọng số) Danh sách kề giúp tìm nhanh các cạnh xuất phát từ một đỉnh cụ thể

+ Đống tối thiểu (Min-Heap) hoặc Queue ưu tiên (Priority Queue): Để tìm

đỉnh có trọng số nhỏ nhất nhanh chóng, Dijkstra thường sử dụng đống tối thiểu hoặc queue ưu tiên Khi thuật toán cần chọn đỉnh với khoảng cách ngắn nhất để xử lý tiếp,

nó lấy đỉnh từ đống tối thiểu

+ Mảng khoảng cách (Distance Array): Mảng này lưu trữ khoảng cách ngắn

nhất hiện tại từ đỉnh nguồn đến mỗi đỉnh khác Khi thuật toán tìm thấy đường đi ngắn hơn, mảng này được cập nhật

+ Mảng đỉnh trước (Predecessor Array): Mảng này lưu trữ đỉnh trước đó trên

đường đi ngắn nhất từ đỉnh nguồn Điều này giúp tái tạo đường đi ngắn nhất sau khi thuật toán hoàn thành

Floyd-Warshall sử dụng các cấu trúc dữ liệu sau:

+ Ma trận khoảng cách (Distance Matrix): Đây là ma trận n x n (n là số đỉnh

trong đồ thị), với dist[i][j] là khoảng cách ngắn nhất từ đỉnh i đến j Ma trận này được khởi tạo với trọng số cạnh hoặc vô cực (inf) nếu không có cạnh trực tiếp, và 0 cho các đường từ một đỉnh đến chính nó

+ Ma trận đỉnh trung gian (Predecessor Matrix): Đây là một ma trận n x n,

cho phép theo dõi đỉnh trung gian trên đường đi ngắn nhất giữa các cặp đỉnh Khi tìm đường đi ngắn nhất giữa hai đỉnh, thuật toán sử dụng ma trận này để tái tạo đường đi

+ Vòng lặp lồng nhau ba lần: Mặc dù không phải là cấu trúc dữ liệu, thuật

toán Floyd-Warshall sử dụng ba vòng lặp lồng nhau để cập nhật ma trận khoảng cách dựa trên đỉnh trung gian k

3.3 Thuật toán

3.3.1 Dijkstra

1 Khởi tạo các biến:

1 int u,minp;

‘u’ là biến lưu chỉ số của đỉnh đang được xét trong quá trình tìm kiếm

‘minp’ là biến lưu giá trị khoảng cách nhỏ nhất tạm thời

Trang 7

2 Khởi tạo khoảng cách và trạng thái cho các đỉnh:

‘d[v]’ là mảng lưu khoảng cách ngắn nhất từ đỉnh nguồn ‘s’ đến đỉnh ‘v’ Ban

đầu, khoảng cách từ ‘s’ đến ‘v’ được khởi tạo bằng giá trị trong ma trận trọng số

‘matran[s][v]’

‘truoc[v]’ là mảng lưu đỉnh trước của đỉnh ‘v’ trên đường đi ngắn nhất từ ‘s’

đến ‘v’ Ban đầu, tất cả các đỉnh đều có đỉnh trước là ‘s’

‘chuaxet[v]’ là mảng đánh dấu trạng thái của đỉnh ‘v’ Ban đầu, tất cả các đỉnh đều chưa được xét (FALSE)

3 Khởi tạo đỉnh nguồn:

truoc[s] = 0 để biểu thị rằng ‘s’ không có đỉnh trước (điểm bắt đầu)

4 Vòng lặp chính của thuật toán Dijkstra:

Trang 8

Vòng lặp tiếp tục cho đến khi đỉnh đích ‘t’ đã được xét (chuaxet[t] là TRUE)

Trong mỗi vòng lặp, tìm đỉnh ‘u’ chưa được xét có khoảng cách nhỏ nhất

(minp) từ đỉnh nguồn

Đánh dấu đỉnh ‘u’ đã được xét (chuaxet[u] = TRUE)

Nếu đỉnh đích ‘t’ chưa được xét, cập nhật khoảng cách của các đỉnh kề với u:

Nếu đường đi qua ‘u’ đến ‘v’ ngắn hơn đường đi hiện tại (d[u] +

matran[u][v] < d[v]), cập nhật khoảng cách mới (d[v] = d[u] + matran[u][v]) và cập nhật đỉnh trước (truoc[v] = u)

Trước tiên chúng ta phải hiểu rõ nội dung của phương pháp Dijkstra:

(Trích từ tài liệu TS Nguyễn Văn Hiệu, Bài tham khảo Đồ án lập trình tính toán – tìm đường đi ngắn nhất với Dijkstra, Khoa CNTT ĐHBK – ĐHĐN)

Thuật toán Dijkstra

Cho G=(X,E) là một đồ thị có trọng không âm gồm n đỉnh Thuật toán Dijkstra được dùng để tìm đường đi ngắn nhất từ đỉnh i đến j cho trước

Gọi L là ma trận trọng lượng (với qui ước Lhk = +∞ nếu không có cạnh nối từ đỉnh

h đến đỉnh k) Ta sử dụng thêm hai mảng để lưu vết của quá trình tìm đường đi:

- Length[…] : lưu độ dài từ đỉnh đầu i đến các đỉnh trong đồ thị

- Last[…] : lưu đỉnh liền trước nó trên đường đi

Trang 9

Chú ý: Khi thuật toán dừng, nếu Length[j] = +∞ thì không tồn tại đường đi từ i đến j,

nếu ngược lại thì Length[j] là độ dài đường đi ngắn nhất

Trang 10

Bước 4: Các đỉnh còn lại đều có Length[] cực đại nên ta cập nhật lại như sau

Trang 11

Last[1] 2 = −1

3 Length[3]2 = 5

Bước 4 (lần 2): tính độ dài từ đỉnh 0 vừa xét ở trên đến các đỉnh còn lại trong T.

Ðỉnh số 3 có chi phí mới là 2+3=5 nhỏ hơn chi phí cũ (6), vì vậy ta cập nhật lại đỉnh này.

Trang 13

3 Length[3] 4 = 5

2 Vòng lặp chính của thuật toán Floyd – Warshall:

1 void floydWarshall(int n) {

Trang 14

• Cập nhật a[i][j] qua đỉnh k:

• Nếu a[i][k] + a[k][j] < a[i][j] với a[i][k] và a[k][j] tồn tại đường đi, thì a[i][j] = a[i][k] + a[k][j] (được hiểu là nếu qua đỉnh k đường đi ngắn hơn thì chọn đường đi qua k)

• Và cập nhật next1[i][j]

• Trong trường hợp ngược lại thì sẽ không cập nhật qua điểm k “next1[i][j]” lưu trữ đỉnh kế tiếp trên đường đi ngắn nhất từ đỉnh i tới đỉnh j:

• Dùng để biểu diễn ma trận khoảng cách trong thuật toán

• Dùng để kết luận đơn giản hơn

Trước tiên chúng ta phải hiểu rõ nội dung của phương pháp Floyd - Warshall:

Bước 1 Xác định ma trận trọng số D và ma trận đường đi P

Bước 2 Cập nhật ma trận trọng số và ma trận đường đi qua từng đỉnh k

Trang 15

Bước 3 Viết ma trận trọng số sau khi cập nhật và kết luận thông qua ma trận đường đi

Ví dụ Floyd – Warshall

Trang 16

Cập nhật qua đình A: Ma trận khoảng cách:

Cập nhật qua đỉnh B: Ma trận khoảng cách:

Trang 17

Cập nhật qua đỉnh C: Ma trận khoảng cách:

Cập nhật qua đỉnh D: Ma trận khoảng cách:

Trang 18

Cập nhật qua đỉnh E: Ma trận khoảng cách:

Cập nhật qua đỉnh F: Ma trận khoảng cách:

Từ ma trận khoảng cách sau khi cập nhật qua tất cả cách đỉnh, ta kết luận:

- Không có đường đi từ A tới A

- Đường đi từ A tới B: A B

- Đường đi từ A tới C: A C

- Đường đi từ A tới D: A B D

- Đường đi từ A tới E: A B E

- Đường đi từ A tới F: A B E F

- Không có đường đi từ B tới A

- Không có đường đi từ B tới B

Trang 19

- Không có đường đi từ B tới C

- Đường đi từ B tới D: B D

- Đường đi từ B tới E: B E

- Đường đi từ B tới F: B E F

- Không có đường đi từ C tới A

- Không có đường đi từ C tới B

- Không có đường đi từ C tới C

- Đường đi từ C tới D: C D

- Đường đi từ C tới E: C E

- Đường đi từ C tới F: C E F

- Không có đường đi từ D tới A

- Không có đường đi từ D tới B

- Không có đường đi từ D tới C

- Không có đường đi từ D tới D

- Đường đi từ D tới E: D E

- Đường đi từ D tới F: D F

- Không có đường đi từ E tới A

- Không có đường đi từ E tới B

- Không có đường đi từ E tới C

- Không có đường đi từ E tới D

- Không có đường đi từ E tới E

- Đường đi từ E tới F: E F

- Không có đường đi từ F tới A

- Không có đường đi từ F tới B

- Không có đường đi từ F tới C

- Không có đường đi từ F tới D

- Không có đường đi từ F tới E

- Không có đường đi từ F tới F

Trang 20

thống, mặc dù nó cũng được dùng cho việc viết các ứng dụng Ngoài ra, C cũng

thường được dùng làm phương tiện giảng dạy trong khoa học máy tính

Trang 21

Hình 2 Kết quả thực thi chương trình

Hình 3 Kết quả nhận được ở file xuatfiledijkstra.txt

Hình 4 Đồ thị của bài toán

Trang 22

Chương trình Floyd – Warshall

Hình 5 Đồ thị bài toán

Hình 6 Nhập số đỉnh và ma trận trọng số của đồ thị trên (999 được hiểu là vô cùng)

Hình 7 Kết quả thực thi chương trình

Kết quả trên được hiểu với A=1,B=2,C=3,D=4,E=5,F=6

Trang 23

Nhận xét đánh giá : Từ đồ thị ta có thể thấy, chương trình đã giải quyết hoàn toàn

chính xác kết quả

4.4 So sánh hai thuật toán

1 Thuật toán Dijkstra

Đặc điểm chính:

đỉnh khác trong đồ thị (đơn nguồn)

• Với cấu trúc dữ liệu hàng đợi ưu tiên (priority queue) và đống (heap), độ phức tạp là 𝑂((𝑉+𝐸)log𝑉), trong đó 𝑉 là số đỉnh và 𝐸 là số cạnh

đỉnh có đường đi ngắn nhất hiện tại chưa được xử lý và cập nhật khoảng cách đến các đỉnh lân cận

• Không xử lý được đồ thị có cạnh trọng số âm

• Chỉ tìm được đường đi ngắn nhất từ một đỉnh nguồn, không phải giữa mọi cặp đỉnh

2 Thuật toán Floyd-Warshall

Đặc điểm chính:

thị

âm (negative weight cycle)

Độ phức tạp thời gian: 𝑂(𝑉3) , trong đó 𝑉 là số đỉnh

cập nhật dần các khoảng cách ngắn nhất giữa tất cả các cặp đỉnh bằng cách xét từng đỉnh trung gian

Trang 24

Ưu điểm:

• Tìm đường đi ngắn nhất giữa mọi cặp đỉnh

• Xử lý được đồ thị có cạnh trọng số âm (miễn là không có chu trình âm)

Nhược điểm:

• Độ phức tạp thời gian cao 𝑂(𝑉3) , không hiệu quả với đồ thị lớn

• Sử dụng nhiều bộ nhớ vì cần lưu ma trận khoảng cách giữa tất cả các cặp đỉnh

So sánh chi tiết

Khi nào nên sử dụng thuật toán nào?

có cạnh trọng số âm Thích hợp cho các đồ thị thưa với nhiều đỉnh và ít cạnh

đồ thị có thể có trọng số âm (không chu trình âm) Thích hợp cho các đồ thị nhỏ hoặc khi cần tính khoảng cách giữa mọi cặp đỉnh

Tóm lại: Cả hai thuật toán Dijkstra và Floyd-Warshall đều có những ứng

dụng và ưu điểm riêng tùy vào yêu cầu cụ thể của bài toán và đặc điểm của đồ thị Việc lựa chọn thuật toán phù hợp sẽ giúp tối ưu hóa hiệu suất và tài

Trang 25

đòi hỏi chúng ta phải có một lượng kiến thức nhất định để thực hiệnĐây là một đề tài

có thể nói là khá hay

5.2 Hướng phát triển

Nhóm nhận thấy phần code của nhóm còn khá dài, chưa được tối ưu một cách

tốt nhất để giảm độ dài cho chương trình cũng như giảm độ phức tạp của thuật toán do

đó nhóm sẽ tiếp tục tìm hiểu và nghiên cứu để làm sao có thể cải thiện được thuật toán

và giải quyết được vấn đề đã nêu ở trên Sử dụng các ngôn ngữ bậc cao hơn để có thể

sử dụng luôn cấu trúc dữ liệu có trong các thư viện, ít mất thời gian thiết lập và bớt

phức tạp hơn

===========================Thanks=============================

Trang 26

TÀI LIỆU THAM KHẢO

[1] Nguyễn Văn Hiệu, Giáo trình Toán Rời Rạc, Đại học Bách Khoa - Đại học

Đà Nẵng

[2] Đặng Thiên Bình, Cấu trúc dữ liệu, Đại học Bách Khoa - Đại học Đà Nẵng [3] Nguyễn Thị Lệ Quyên, Giáo trình Kĩ thuật lập trình, Đại học Bách Khoa - Đại học Đà Nẵng

Trang 27

27 printf( "Do dai duong di ngan nhat tu %d den %d la: %d\n" ,s,t,d[t]);

28 fprintf(f, "Do dai duong di ngan nhat tu %d den %d la: %d\n" ,s,t,d[t]);

29 printf( "Duong di ngan nhat tu %d den %d la:\n" ,s,t);

30 fprintf(f, "Duong di ngan nhat tu %d den %d la:\n" ,s,t);

Trang 28

9 void khoiTaoMaTran(int n);

10 void floydWarshall(int n);

11 void inMaTranKhoangCach(int n);

12 void inDuongDi(int n);

27 void khoiTaoMaTran(int n) {

28 printf( "Nhap ma tran trong so cua do thi (nhap %d cho vo cung): \n" , oo);

29 for (int i = 1; i <= n; i++) {

Trang 29

43 for (int i = 1; i <= n; i++) {

54 void inMaTranKhoangCach(int n) {

55 FILE* fpOut = fopen( "floydwarshall.txt" , "w" );

56 printf( "Ma tran khoang cach:\n" );

57 fprintf(fpOut, "Ma tran khoang cach:\n" );

58 for (int i = 1; i <= n; i++) {

70 void inDuongDi(int n) {

71 FILE* fpOut = fopen( "floydwarshall.txt" , "a" );

79 printf( "Khong có duong di tu %d toi %d\n" , i, j);

80 fprintf(fpOut, "Khong co duong di tu %d toi %d\n" , i, j);

81 continue;

82 }

83 if (i != j) {

84 printf( "Duong di tu %d toi %d: " , i, j);

85 fprintf(fpOut, "Duong di tu %d toi %d: " , i, j);

Ngày đăng: 29/11/2024, 16:18

TÀI LIỆU CÙNG NGƯỜI DÙNG

TÀI LIỆU LIÊN QUAN

w