Thuật toán tìm kiếm đường đi ngắn nhất chi phí thấp nhất trên đồ thị có trọng số theo Dijkstra: .... LỜI MỞ ĐẦU Đề tài: “Mô phỏng thuật toán tìm kiếm đồ thị theo DFS và BFS và tìm đườn
Trang 1LỜI CẢM ƠN
Ngày nay, việc ứng dụng công nghệ thông tin đã trở nên phổ biến trong hầu hết mọi
cơ quan, doanh nghiệp, trường học đặc biệt là việc áp dụng các giải pháp tin học trong công tác quản lý
Trong ít năm trở lại đây, với tốc độ phát triển như vũ bão, CNTT đang dần làm cho cuộc sống của con người trở nên thú vị và đơn giản hơn Vì vậy để bắt kịp với nhịp độ phát triển của xã hội, những kiến thức học được trên giảng đường là vô cùng quan trọng đối với mỗi Sinh viên chúng em
Chúng em xin chân thành cảm ơn các thầy, cô bộ môn đã tận tình giảng dạy chúng
em trong suốt thời gian học tập vừa qua Nhờ có sự chỉ dạy tận tình của các thầy, cô để giúp chúng em hoàn thành đồ án này Tuy đã có nhiều cố gắng nhưng trong quá trình làm
đề tài không thể tránh được những sai sót Chúng em rất mong nhận được các ý kiến đóng góp từ các thầy cô và bạn bè để chúng em rút kinh nghiệm thực hiện tốt hơn ở các đề tài
sau
Chúng em xin chân thành cảm ơn thầy cô!
Trang 2MỤC LỤC
CHƯƠNG 1: CƠ SỞ LÝ THUYẾT 1
1.1 Lý thuyết ngăn xếp (Stack) 1
1.2 Lý thuyết hàng đợi (Queue) 1
1.3 Thuật toán tìm kiếm theo chiều sâu ( DFS: Depth First Search ) 2
1.3.1 Ý tưởng thuật toán 2
1.3.2 Phân tích thuật toán 3
1.3.3 Ví dụ DFS 3
1.4 Thuật toán tìm kiếm theo chiều rộng (BFS: Breadth First Search) 4
1.4.1 Ý tưởng thuật toán 4
1.4.2 Phân tích thuật toán 4
1.4.3 Ví dụ BFS 5
1.5 Thuật toán tìm kiếm đường đi ngắn nhất (chi phí thấp nhất) trên đồ thị có trọng số theo Dijkstra: 5
1.5.1 Ý tưởng của thuật toán Dijkstra: 5
1.5.2 Phân tích thuật toán: 6
1.5.3 Ví dụ Dijkstra : 6
1.6 Lý thuyết đồ họa trong Java: 8
1.6.1 Tìm tọa độ các đỉnh của đa giác đều n đỉnh 8
1.6.2 Thuật toán vẽ đường thẳng nối 2 điểm 9
CHƯƠNG 2: PHÂN TÍCH - THIẾT KẾ 10
2.1 Phân tích chương trình 10
2.1.1 Yêu cầu: 10
2.1.2 Quy định: 10
2.1.2 Mục tiêu đặt ra: 10
2.2 Thiết kế 11
Trang 32.2.1 Kiến trúc chung 11
2.2.1.1 Chức năng khởi tạo: 12
2.2.1.2 Chức năng tìm kiếm đồ thị BFS và DFS: 12
2.2.1.3 Chức năng tìm kiếm đồ thị Dijkstra (đồ thị vô hướng): 12
2.2.1.4 Chức năng mô phỏng tìm kiếm đồ thị: 12
2.2.2 Cấu trúc dữ liệu 13
2.2.3 Thuật toán 13
2.2.3.1 Lưu đồ giải thuật BFS: 13
2.2.3.2 Lưu đồ giải thuật DFS: 15
2.2.3.3 Lưu đồ giải thuật Dijkstra: 16
2.2.3.4 Lưu đồ giải thuật Bresenham 17
2.2.4 Giao diện người dùng 18
2.3 Mục tiêu đã hoàn thành 19
2.4 Các thiếu sót của chương trình 21
2.5 Khả năng ứng dụng 21
2.6 Hướng phát tiển 21
KẾT LUẬN 22
TÀI LIỆU THAM KHẢO 24
NHẬN XÉT CỦA GIÁO VIÊN HƯỚNG DẪN 25
Trang 4DANH MỤC HÌNH ẢNH
Hình 1 1: Mô tả ngăn xếp 1
Hình 1 2: Mô tả hàng đợi 2
Hình 1 3: Đồ thị tìm kiếm DFS 4
Hình 1 4: Ví dụ BFS 5
Hình 1 5: Ví dụ thuật toán Dijkstra 7
Hình 1 6: Minh họa thuật toán Bresenham 9
Hình 2 1: Mô hình phân rã chức năng 11
Hình 2 2: Lưu đồ giải thuật BFS 14
Hình 2 3: Lưu đồ giải thuật DFS 15
Hình 2 4: Lưu đồ giải thuật Dijkstra 16
Hình 2 5: Lưu đồ thuật toán Bresenham 17
Hình 2 6: Giao diện chính 20
Hình 2 7: Giao diện mô phỏng thuật toán Dijkstra 20
Trang 5LỜI MỞ ĐẦU
Đề tài: “Mô phỏng thuật toán tìm kiếm đồ thị theo DFS và BFS và tìm đường đi ngắn nhất trên đồ thị có trọng số theo Dijkstra”
Lý thuyết đồ thị là một lĩnh vực đã có từ lâu và có nhiều ứng dụng hiện đại Những
tư tưởng cơ bản của lý thuyết đồ thị được đề xuất vào những năm đầu của thế kỷ 18 bởi nhà toán học lỗi lạc người Thụy Sỹ Lenhard Eurler Chính ông là người đã sử dụng đồ thị
để giải bài toán nổi tiếng về các cái cầu ở thành phố Konigsberg…
Có rất nhiều thuận toán trên đồ thị được xây dựng trên cơ sở duyệt tất cả các đỉnh của đồ thị sao cho mỗi đỉnh của nó được viếng thăm đúng một lần Vì vậy, việc xây dựng những thuật toán cho phép duyệt một cách hệ thống tất cả các đỉnh của đồ thị là một vấn
đề quan trọng thu hút sự quan tâm nghiên cứu của nhiều người Những thuật toán như vậy được gọi chung là thuật toán tìm kiếm trên đồ thị Trong đề tài này chúng em sẽ giới thiệu hai thuật toán tìm kiếm cơ bản trên đồ thị không trọng số: Thuật toán tìm kiếm theo chiều sâu (Depth Firt Search), Thuật toán tìm kiếm theo chiều rộng (Breadth First Search) và thuật toán tìm đường đi ngắn nhất (chi phí thấp nhất) trên đồ thị có trọng số theo Dijkstra
Để tiện cho việc học tập, tìm hiểu 3 thuật toán trên, nhóm chúng em đã viết một chương trình sử dụng ngôn ngữ lập trình Java để mô phỏng lại 3 thuật toán BFS và DFS
và Dijkstra Người dùng nhập dữ liệu vào chương trình qua giao diện cửa sổ và lựa chọn chức năng của chương trình là tìm kiếm theo chiều rộng (BFS) hoặc tìm kiếm theo chiều sâu (DFS) hoặc tìm đường đi ngắn nhất (chi phí thấp nhất) trên đồ thị có trọng số theo Dijkstra Sau khi có kết quả tìm kiếm, người dùng có thể lựa chọn mô phỏng lại quá trình tìm kiếm bằng giao diện đồ họa Chương trình sẽ vẽ một đồ thị dựa trên những dữ liệu của người dùng và hiển thị quá trình tìm kiếm bằng hình ảnh động để người dùng có thể
thấy được quá trình tìm kiếm diễn ra theo từng bước như thế nào
Cấu trúc của báo cáo đề tài gồm 4 chương:
- Chương 1: Cơ sở lý thuyết
- Chương 2: Phân tích - Thiết kế
- Kết luận
Trang 6CHƯƠNG 1: CƠ SỞ LÝ THUYẾT 1.1 Lý thuyết ngăn xếp (Stack)
Một ngăn xếp là một cấu trúc dữ liệu dạng thùng chứa (container) của các phần tử
(thường gọi là các nút (node)) và có hai phép toán cơ bản : push and pop Push bổ sung
một phần tử vào đỉnh (top) của ngăn xếp, nghĩa là sau các phần tử đã có trong ngăn xếp
Pop giải phóng và trả về phần tử đang đứng ở đỉnh của ngăn xếp Trong stack, các đối
tượng có thể được thêm vào stack bất kỳ lúc nào nhưng chỉ có đối tượng thêm vào sau cùng mới được phép lấy ra khỏi stack
Ngoài ra, stack cũng hỗ trợ một số thao tác khác:
isEmpty(): Kiểm tra xem stack có rỗng không
Top(): Trả về giá trị của phần tử nằm ở đầu stack mà không hủy nó khỏi stack Nếu stack rỗng thì lỗi sẽ xảy ra
Hình 1 1: Mô tả ngăn xếp
1.2 Lý thuyết hàng đợi (Queue)
Một hàng đợi là một cấu trúc dữ liệu dạng ống của các phần tử (thường gọi là các
nút (node)) và có hai phép toán cơ bản : enQueue and deQueue enQueue bổ sung một phần tử vào cuối của hàng đợi, nghĩa là sau các phần tử đã có trong hàng đợi deQueue
giải phóng và trả về phần tử đang đứng ở đỉnh của hàng đợi Trong Queue, các đối tượng
Trang 7có thể được thêm vào Queue bất kỳ lúc nào nhưng chỉ có đối tượng thêm vào đầu tiên mới được phép lấy ra khỏi stack
Ngoài ra, stack cũng hỗ trợ một số thao tác khác:
IsEmpty(): kiểm tra xem hàng đợi có rỗng không
Front(): trả về giá trị của phần tử nằm ở đầu hàng đợi mà không hủy nó Nếu hàng đợi rỗng thì lỗi sẽ xảy ra
Thông thường, DFS là một dạng tìm kiếm mù mà quá trình tìm kiếm được phát triển tới đỉnh con đầu tiên của nút đang tìm kiếm cho tới khi gặp được đỉnh cần tìm hoặc tới một nút không có con Khi đó giải thuật quay lui về đỉnh vừa mới tìm kiếm ở bước trước Trong dạng không đệ quy, tất cả các đỉnh chờ được phát triển được bổ sung và một ngăn xếp Đỉnh nào được đưa vào ngăn xếp sau sẽ được phát triển trước (LIFO)
Trang 8Ý tưởng chính của thuật toán có thể trình bày như sau:
Ta sẽ bắt đầu tìm kiếm từ một đỉnh v0 nào đó của đồ thị Sau đó chọn u là một đỉnh tuỳ ý kề với v0 và lặp lại quá trình đối với u Ở bước tổng quát, giả sử ta đang xét đỉnh v Nếu như trong số các đỉnh kề với v tìm được đỉnh w là chưa được xét thì ta sẽ xét đỉnh này (nó sẽ trở thành đã xét) và bắt đầu từ nó ta sẽ bắt đầu quá trình tìm kiếm còn nếu như không còn đỉnh nào kề với v là chưa xét thì ta nói rằng đỉnh này đã duyệt xong và quay trở lại tiếp tục tìm kiếm từ đỉnh mà trước đó ta đến được đỉnh v (nếu v=v0, thì kết thúc tìm kiếm) Có thể nói nôm na là tìm kiếm theo chiều sâu bắt đầu từ đỉnh v được thực hiện trên cơ sở tìm kiếm theo chiều sâu từ tất cả các đỉnh chưa xét kề với v
1.3.2 Phân tích thuật toán
Các bước thực hiện thuật giải:
- Bước 1 Xuất phát từ đỉnh bắt đầu, chuyển đỉnh đó vào ngăn xếp
- Bước 2 Xử lý đỉnh này và đánh dấu để không xử lý lần sau
- Bước 3 Chuyển tất cả các đỉnh kề nó vào ngăn xếp, chọn một đỉnh để xử lý tiếp theo
- Bước 4 Quay lại bước 2 cho đến khi tìm được đường ngắn nhất hoặc không còn đỉnh trong ngăn xếp
1.3.3 Ví dụ DFS
Ví dụ:
1 Bắt đầu từ đỉnh 1 Đưa các đỉnh kề với 1 vào danh sách: 2, 4, 5
2 Chọn đỉnh 2 để xử lý Đưa các đỉnh kề với đỉnh 2 vào danh sách: 3, 5
3 Chọn đỉnh 3 để xử lý Đưa các đỉnh kề với đỉnh 3 vào danh sách: 5, 6
4 Chọn định 5 để xử lý Đưa các đỉnh kề với đỉnh 5 vào danh sách: 4
5 Chọn đỉnh 4 để xử lý Không có đỉnh nào kề với đỉnh 4
6 Chọn đỉnh 6 để xử lý Không có đỉnh nào kề với đỉnh 6
7 Tất các đỉnh đã được duyệt
8 Kết quả cuối cùng thứ tự: 1 2 3 5 4 6
Đồ thị:
Trang 9Để ý rằng trong thuật toán tìm kiếm theo chiều sâu đỉnh được thăm càng muộn sẽ càng sớm trở thành đã duyệt xong Điều đó là hệ quả tất yếu của việc các đỉnh được thăm
sẽ được kết nạp vào trong ngăn xếp (STACK) Tìm kiếm theo chiều rộng trên đồ thị, nếu nói một cách ngắn gọn, được xây dựng trên cơ sở thay thế ngăn xếp (STACK) bởi hàng đợi (QUEUE) Với sự cải biên như vậy, đỉnh được thăm càng sớm sẽ càng sớm trở thành
đã duyệt xong (tức là càng sớm dời khỏi hàng đợi) Một đỉnh sẽ trở thành đã duyệt xong ngay sau khi ta xét xong tất cả các đỉnh kề (chưa được thăm) với nó
1.4.2 Phân tích thuật toán
Bước 1: Khởi tạo:
- Các đỉnh đều ở trạng thái chưa đánh dấu, ngoại trừ đỉnh xuất phát S là đã đánh dấu duyệt theo thứ tự ưu tiên chiều rộng
Bước 2: Lặp các bước sau đến khi hàng đợi rỗng:
- Lấy u khỏi hàng đợi, thông báo thăm u (Bắt đầu việc duyệt đỉnh u) Xét tất cả những đỉnh v kề với u mà chưa được đánh dấu, với mỗi đỉnh v đó:
- Đánh dấu v
- Ghi nhận vết đường đi từ u tới v (Có thể làm chung với việc đánh dấu)
- Đẩy v vào hàng đợi (v sẽ chờ được duyệt tại những bước sau)
Bước 3: Truy vết tìm đường đi
Trang 10Các đỉnh v kề u
mà chưa lên lịch
Hàng đợi sau khi đẩy những đỉnh v vào
đi từ S tới F sẽ là đường đi ngắn nhất (theo nghĩa qua ít cạnh nhất)
1.5 Thuật toán tìm kiếm đường đi ngắn nhất (chi phí thấp nhất) trên đồ thị có trọng
số theo Dijkstra:
1.5.1 Ý tưởng của thuật toán Dijkstra:
Có rất nhiều giải thuật đã được phát triển để giải bài toán tìm đường đi ngắn nhất giữa một cặp đỉnh, trong khuôn khổ bài viết này tôi chỉ xin giới thiệu giải thuật Dijkstra Giải thuật Dijkstra là một giải thuật để giải bài toán đường đi ngắn nhất nguồn đơn trên
Trang 11một đồ thị có trọng số cạnh mà tất cả các trọng số đều không âm Nó xác định đường đi
ngắn nhất giữa hai đỉnh cho trước, từ đỉnh a đến đỉnh b
Ý tưởng xuất phát từ việc gán nhãn cho các đỉnh Nhãn được gán theo cách là đường
đi ngắn nhất từ đỉnh a đến đỉnh đó Đến khi nào đỉnh b là đỉnh được chọn tức là đường đi
từ a tới b là ngắn nhất, ta truy vết để tìm đường đi ngắn nhất (chi phí thấp nhất)
1.5.2 Phân tích thuật toán:
Ở mỗi đỉnh v, giải thuật Dijkstra xác định 3 thông tin: k v , d v và p v
kv: mang giá trị boolean xác định trạng thái được chọn của đỉnh v
Ban đầu ta khởi tạo tất cả các đỉnh v chưa được chọn, nghĩa là:
k v = false, v V
d v : là chiều dài đường đi mà ta tìm thấy cho đến thời điểm đang xét từ a đến v
Khởi tạo, d v = , v V \{a}, d a = 0
p v : là đỉnh trước của đỉnh v trên đường đi ngắn nhất từ a đến b Đường đi ngắn nhất từ
a đến b có dạng {a, ,p v ,v, ,b} Khởi tạo, p v = null, v V
Sau đây là các bước của giải thuật Dijkstra
Bước 1 Khởi tạo: Đặt k v := false v V; d v := ,v V \ {a}, d a :=0
Bước 2 Chọn v V sao cho k v = false và d v = min {d t / t V, k t = false}
Nếu d v = thì kết thúc, không tồn tại đường đi từ a đến b
Bước 3 Đánh dấu đỉnh v, k v: = true
Bước 4 Nếu v = b thì kết thúc và d b là độ dài đường đi ngắn nhất từ a đến b Ngược lại nếu v b sang Bước 5
Bước 5 Với mỗi đỉnh v kề với u mà k v = false, kiểm tra
Nếu d v > d u + w(u,v) thì d v := d u + w(u,v) Ghi nhớ đỉnh v: p u := v.Quay lại Bước 2
1.5.3 Ví dụ Dijkstra :
Ví dụ:
Trang 12Ta có đồ thị nhƣ sau: tìm đường đi ngắn nhất từ a tới f
Hình 1 5: Ví dụ thuật toán Dijkstra
- Gán nhãn đỉnh a là 0, đánh dấu a được xét; nhãn của các đỉnh còn lại là vô cùng, đánh dấu là chưa được xét
- Gán lại nhãn của các đỉnh kề đỉnh đang xét (đỉnh a) mà chưa được chọn với điều kiện nhãn mới có giá trị nhỏ hơn nhãn cũ (nhãn mới = nhãn của đỉnh chọn + trọng số)
- Tìm đỉnh có nhãn nhỏ nhất mà chưa được chọn, đánh dấu là được xét Thực hiện lại cho đến khi đỉnh cuồi (đỉnh f) là đỉnh được chọn
Ta lập được bảng như sau:
Trang 131.6 Lý thuyết đồ họa trong Java:
1.6.1 Tìm tọa độ các đỉnh của đa giác đều n đỉnh
class Point{
public int x,y;
Point(int xx,int yy){
}
}
Point Tips[] = new Point[n];
Point CenterPoint = new Point( 300 , 300);
int Radius = BFSFrame.WIDTH/2-50;
float Angle = 2f * (float)Math.PI / (float)n;
for (int i = 0; i < n; i++)
{
Tips[i] = new Point(0,0);
Tips[i].x = (int)((float)CenterPoint.x + 4*(Radius * (float)Math.sin(i *
Angle)));
Tips[i].y = (int)((float)CenterPoint.y - 4*(Radius * (float)Math.cos(i *
Angle)));
}
Khởi tạo mảng Point[n] gồm n đỉnh
Xác định điểm trung tâm CenterPoint Do trong phần mềm, chúng ta cố định kích thước của frame là 600x600 nên điểm trung tâm là Point(300,300);
Radius : bán kính của đường tròn ngoại tiếp đa giác đều tâm là CenterPoint Trong bài này chúng ta chọn là 250
Angle : là góc quay sau của mỗi đỉnh so với đỉnh đầu tiên (tính theo rad) (với 2f tương đương 2.0) => góc quay bằng 2pi/n
Trang 141.6.2 Thuật toán vẽ đường thẳng nối 2 điểm
Thuật toán Bresenham
Với thuật toán DDA, việc quyết định chọn yi+1 là yi hay yi+1, dựa vào phương trình của đoạn thẳng y = mx + b Còn với thuật toán Bresenham đưa ra cách chọn yi+1 là y-
i hay yi +1 theo một hướng khác sao cho có thể tối ưu hóa về mặt tốc độ so với thuật toán DDA Vấn đề mấu chốt ở đây là làm thế nào để hạn chế tối đa các phép toán trên số thực trong thuật toán
Hình 1 6: Minh họa thuật toán Bresenham
Trang 15CHƯƠNG 2: PHÂN TÍCH - THIẾT KẾ 2.1 Phân tích chương trình
Xây dựng 3 chức năng chính của chương trình là tìm kiếm theo chiều rộng (BFS) hoặc tìm kiếm theo chiều sâu (DFS) hoặc tìm đường đi ngắn nhất (chi phí thấp nhất) trên
đồ thị có trọng số theo Dijkstra với dữ liệu là ma trận đỉnh và liên kết nhập từ trên
Sau khi có kết quả tìm kiếm, người dùng có thể lựa chọn mô phỏng lại quá trình tìm kiếm bằng giao diện đồ họa Chương trình sẽ vẽ một đồ thị dựa trên những dữ liệu của người dùng và hiển thị quá trình tìm kiếm bằng hình ảnh động để người dùng có thể thấy được quá trình tìm kiếm diễn ra theo từng bước như thế nào
2.1.2 Quy định:
Số lượng đỉnh 2<= n <=20 để tránh tràn bộ nhớ
Với dijkstra, Yêu cầu dữ liệu nhập vào là trọng số đường đi giữa các đỉnh phải là số dương, Nếu giá trị = 0 tức là không có đường đi nối giữa 2 đỉnh
Với BFS và DFS, giá trị của ma trận liên lạc =0 tức là không có đường đi, = 1 tức là
có đường đi nối giữa 2 đỉnh
Giá trị của đỉnh khởi đầu và đỉnh kết thúc phải là số và < n (tránh xét đỉnh không tồn tại)
Thời gian hiển thị của mô phỏng không quá nhanh cũng không quá chậm
2.1.2 Mục tiêu đặt ra:
Yêu cầu về thời gian:
Tốc độ xử lý của chương trình phụ thuộc vào độ phức tạp của thuật toán được sử dụng Cần tối ưu thuật toán về mặt thời gian và không gian lưu trữ