Tìm đường đi bằng BFS¶
Tóm lược nội dung
Bài này trình bày bài toán tìm đường đi bằng thuật toán BFS.
Bài toán¶
Yêu cầu:
Tìm đường đi có khoảng cách ngắn nhất giữa hai đỉnh trong đồ thị bằng cách áp dụng thuật toán BFS. Giả sử tất cả các cạnh đều có độ dài bằng 1.
Đầu vào:
- Dòng đầu tiên gồm các đỉnh của đồ thị.
- Các dòng tiếp theo mô tả các cạnh của đồ thị.
- Hai dòng cuối cùng là hai đỉnh cần tìm khoảng cách ngắn nhất.
Đầu ra:
Khoảng cách ngắn nhất giữa hai đỉnh theo yêu cầu.
Bộ kiểm thử:
| Đầu vào | Đầu ra | Giải thích |
|---|---|---|
| 0 1 2 3 4 5 0 1 0 2 1 2 1 3 1 4 2 4 3 4 3 5 4 5 0 5 |
3 | Đồ thị gồm 6 đỉnh, đánh số từ 0 đến 5. Có 9 cạnh. Hai đỉnh cần tìm khoảng cách ngắn nhất là 0 và 5. |
Đồ thị có thể được phác họa như sau:
Cách giải đề xuất¶
Ý tưởng chính
1. Ta dùng danh sách kề để biểu diễn đồ thị G.
2. Đối với đồ thị không có trọng số như bài này, BFS bảo đảm tìm được khoảng cách ngắn nhất, vì BFS duyệt các đỉnh theo từng lớp, mà các cạnh đều được coi là có cùng độ dài bằng 1.
3. Để đánh dấu các đỉnh đã ghé thăm, ta dùng danh sách visited. Ví dụ: đánh dấu đỉnh v đã ghé thăm bằng dòng lệnh visited.append(v).
4. Để lưu khoảng cách từ đỉnh start đến các đỉnh khác, ta dùng biến distance có kiểu dictionary, trong đó:
key: là tên đỉnh.value: là khoảng cách từ đỉnhstartđến đỉnhkey.
Ví dụ:
distance[0] = 0: khoảng cách từ đỉnh start (là đỉnh 0) đến chính nó là 0.
5. Cách cập nhật khoảng cách:
Khi duyệt một đỉnh v kề với đỉnh current, khoảng cách được tính là: distance[v] = distance[current] + 1.
Ví dụ:
Khi đi từ 0 đến 1: distance[1] = distance[0] + 1 = 1.
Khi đi từ 1 đến 3: distance[3] = distance[1] + 1 = 2.
Khi đi từ 3 đến 5: distance[5] = distance[3] + 1 = 3.
Viết chương trình
0. Khai báo biến data chứa dữ liệu đầu vào.
1. Viết chương trình chính:
- Khởi tạo các biến liên quan.
- Gọi hàm
input()để đọc dữ liệu đầu vào. - Gọi hàm
bfs()để tìm khoảng cách ngắn nhất theo theo thuật toán BFS, gán kết quá cho biếnresult. - In ra biến
result.
-
defaultdictlà cấu trúc tương tựdict, giúp ta không cần quan tâm khóa đã tồn tại hay chưa.Ví dụ:
Xét dòng lệnhadj_list[u].append(v).Với
dictthông thường, chương trình sẽ báo lỗi nếu đỉnhuchưa tồn tại. Muốn tránh lỗi, ta phải viết lệnh kiểm tra như:if u not in adj_list: adj_list[u] = [], rồi mới đượcappend(v).Ngược lại,
defaultdictkhông cần kiểm tra tồn tại, nó sẽ tự tạo danh sách rỗng[]và chạy tiếp.
2. Viết hàm input() dùng để đọc dữ liệu đầu vào data.
-
Dòng lệnh này sẽ yêu cầu chương trình không tạo biến mới, mà dùng hai biến toàn cục đã có.
Ta không cần dùng
globalcho biếnadj_listvì nó làlist.
Lưu ý:
Trong bài toán này, ta ngầm định mọi đỉnh đều có ít nhất một cạnh nối vào. Do đó, ta bỏ qua dòng lệnh đọc danh sách các đỉnh: vertices = list(map(int, lines[0].split())).
3. Viết hàm bfs() dùng để tính khoảng cách từ đỉnh start đến đỉnh finish bằng BFS.
Hàm không có tham số vì ta dùng các biến toàn cục: adj_list, start và finish.
Giá trị trả về là một số nguyên biểu thị khoảng cách ngắn nhất giữa hai đỉnh.
Hàm hoạt động như sau:
-
Khởi tạo các biến:
q: đóng vai trò hàng đợi, lưu các đỉnh trong khi duyệt BFS.visited: lưu các đỉnh đã ghé thăm.distance: lưu khoảng cách từ đỉnhstartđến các đỉnh khác.
-
Bước 1:
- Nạp đỉnh
startvào hàng đợi. - Đánh dấu đỉnh
startđã ghé thăm.
- Nạp đỉnh
-
Bước 2:
Duyệt từng phần tử trong hàng đợi cho đến khi không còn phần tử nào nữa:
- Lấy đỉnh đầu tiên ra khỏi hàng đợi, đặt là đỉnh
current. - Nếu đỉnh
currentlà đỉnhfinishthì trả về khoảng cách, kết thúc thuật toán. -
Ngược lại, nếu không phải
finishthì duyệt từng đỉnhvkề với đỉnhcurrent:Nếu đỉnh v chưa được ghé thăm thì:
- Đánh dấu đỉnh
vđã ghé thăm. - Nạp đỉnh
vvào hàng đợi. - Cập nhật khoảng cách từ
startđếnv.
- Đánh dấu đỉnh
- Lấy đỉnh đầu tiên ra khỏi hàng đợi, đặt là đỉnh
-
Thay vì khai báo
listchovisited, ta khai báosetnhằm cải thiện hiệu suất.Trong Python, việc tìm kiếm như
if v not in visited:khi dùnglistcó độ phức tạp \(O(n)\), trong khi dùngsetthì có độ phức tạp \(O(1)\).
4. Nạp module queue và nạp đối tượng defaultdict của module collections.
Mã nguồn¶
Code đầy đủ được đặt tại:
