HỌC VIỆN CÔNG NGHỆ BƯU CHÍNH VIỄN THÔNGKHOA VIỄN THÔNG BÀI TẬP LỚN TOÁN RỜI RẠC Chủ đề: Thuật toán BFS và bài toán duyệt tất cả các thành phần liên thông của đồ thị Giảng viên: Dương Tha
Trang 1HỌC VIỆN CÔNG NGHỆ BƯU CHÍNH VIỄN THÔNG
KHOA VIỄN THÔNG
BÀI TẬP LỚN TOÁN RỜI RẠC
Chủ đề:
Thuật toán BFS và bài toán duyệt tất cả các
thành phần liên thông của đồ thị
Giảng viên: Dương Thanh Tú
Hà Nội 2022
Trang 2I THUẬT TOÁN TÌM KIẾM THEO CHIỀU RỘNG (BFS)
- Thuật toán duyệt đồ thị ưu tiên chiều rộng (Breadth-first search – BFS) là một trong những thuật toán tìm kiếm cơ bản và thiết yếu trên đồ thị Mà trong đó những đỉnh nào gần đỉnh xuất phát hơn sẽ được duyệt trước
- Ứng dụng của BFS có thể giúp ta giải quyết tốt một số bài toán trong thời gian
và không gian tối thiểu Đặc biệt là bài toán tìm kiếm đường đi ngắn nhất từ một đỉnh gốc tới tất cả các đỉnh khác Trong đồ thị không có trọng số tất cả trọng số bằng nhau, thuật toán sẽ luôn trả ra đường đi ngắn nhất có thể Ngoài
ra, thuật toán này còn được dù để tìm các thành phần liên thông của đồ thị, hoặc kiểm tra đồ thị hai phía,…
II Mô tả thuật toán BFS
Đầu tiên ta thăm đỉnh nguồn ss
Việc thăm đỉnh ss sẽ phát sinh thứ tự thăm các đỉnh (u1,u2,…up)(u1,u2,
…up) kề với ss (những đỉnh gần ss nhất) Tiếp theo, ta thăm đỉnh u1u1, khi thăm đỉnh u1u1 sẽ lại phát sinh yêu cầu thăm những đỉnh (v1,v2,
…,vq)(v1,v2,…,vq) kề với u1u1 Nhưng rõ ràng những đỉnh vv này
“xa” ss hơn những đỉnh uu nên chúng chỉ được thăm khi tất cả những đỉnh uu đều đã được thăm Tức là thứ tự thăm các đỉnh sẽ là: s,u1,u2,
…,up,v1,v2,…,vq,…
Trang 3- Mã giả của thuật toán BFS:
BFS(u){
// Step 1: Khởi tạo
Queue = Ø; // Tạo 1 hàng đợi rỗng.
Push(queue, u); // Đẩy u vào hàng đợi.
Visited[u] = true; // Đánh dấu là u đã được thăm.
// Step2: Lặp đến khi hàng đợi chưa rỗng.
Trang 4While(queue != Ø)
v = pop(queue); // Lấy đỉnh ở đầu hàng đợi và xóa nó khỏi hàng
đợi <Thăm đỉnh v>
/ Duyệt tất cả các đỉnh kề với v mà chưa được thăm và đẩy vào hàng đợi for(int x : ke[v]){
if(!visited[x]){ // Nếu x chưa được
thăm push(queue, x);
visited[x] = true;
EndIf.
EndFor.
EndWhile.
/ Step3: Trả lại kết quả
Return(<tập đỉnh được duyệt>).
End.
Ví dụ: Cho đồ thị G = <V,E>, trình bày quá trình thuật toán BFS bắt đầu chạy từ đỉnh 1
Trang 5STT Trạng thái Queue Các đỉnh được duyệt
Trang 65 10,4,6,7,9,8 1,2,3,5
III, Đánh giá độ phức tạp của thuật toán BFS
Trang 7*)Độ phức tạp thời gian
Gọi |V||V| là số lượng đỉnh và |E||E| là số lượng cạnh của đồ thị
Trong quá trình BFSBFS, cách biểu diễn đồ thị có ảnh hưởng lớn tới chi phí về thời gian thực hiện giải thuật :
Nếu đồ thị biểu diễn bằng danh sách kề (vector g[]) :
o Ta có thể thực hiện thuật toán này một cách tối ưu nhất về mặt thời gian nhờ khả năng duyệt qua các đỉnh kề của mỗi đỉnh một cách hiệu quả
o Vì ta sử dụng mảng visit[] để ngăn việc đẩy một đỉnh vào hàng đợi nhiều lần nên mỗi đỉnh sẽ được thăm chính xác một lần duy nhất
Do đó, ta mất độ phức tạp thời gian O(|V|)O(|V|) dành cho việc thăm các đỉnh
o Bất cứ khi nào một đỉnh được thăm, mọi cạnh kề với đỉnh đó đều được duyệt, với thời gian dành cho mỗi cạnh là O(1)O(1) Từ phần
nhận xét của định lý Bắt tay (Handshaking lemma), ta sẽ mất độ
phức tạp thời gian O(|E|)O(|E|) dành cho việc duyệt các cạnh
o Nhìn chung, độ phức tạp thời gian của thuật toán này là O(|V|+| E|)O(|V|+|E|) Đây là cách cài đặt tốt nhất
Nếu đồ thị được biểu diễn bằng ma trận kề :
o Ta cũng sẽ mất độ phức tạp thời gian O(|V|)O(|V|) dành cho việc
thăm các đỉnh (giải thích tương tự như trên).
o Với mỗi đỉnh được thăm, ta sẽ phải duyệt qua toàn bộ các đỉnh của
đồ thị để kiểm tra đỉnh kề với nó Do đó, thuật toán sẽ mất độ phức tạp O(|V|2)O(|V|2)
Trang 8IV THUẬT TOÁN BFS DUYỆT TẤT CÁC THÀNH PHẦN
LIÊN THÔNG CỦA ĐỒ THỊ
1 Đếm số thành phần liên thông của đồ thị vô hướng
- Nếu đồ thị là liên thông thì số thành phần liên thông của nó là 1 Tương ứng với thủ tục BFS(u) được gọi đúng 1 lần
- Nếu đồ thị không liên thông thì số thành phần không liên thông của nó >1 Suy ra có thể xác định số thành phần liên thông bằng cách gọi hàm BFS
- Thuật toán xác định số thành phần liên thông của đồ thị
Mã giả
Số TPLT(){
//Khởi tạo số TPLT ban đầu của đồ thị = 0
cnt = 0;
//Lặp
for(int i = 1; i <= n; i++) //Duyệt tất cả các đỉnh
Trang 9++cnt; //Tăng số thành phần liên thông
BFS(i);
}
}
return cnt;
}
2 Tính liên thông mạnh trên đồ thị có hướng
- Đối với đồ thị vô hướng, nếu thủ tục BFS(u) = V thì ta kết luận đồ thị vô hướng liên thông
- Đối với đồ thị có hướng, nếu BFS(u) = V thì ta chỉ kết luận có đường đi từ u đến tất cả các đỉnh còn lại của đồ thị Nhiệm vụ là phải kiểm tra BFS(u) = V với mọi u thuộc V
Mã giả
Boolean strong-connective ( G = <V,E> ) {
ReInit(); //Với mọi u thuộc V thì visited[u] = false
Trang 10For each u ∈ V do { //Lấy mỗi đỉnh thuộc V
If(BFS(u) != V)
Return false; //Đồ thị không liên thông mạnh
}
Reinit(); //Khởi tạo lại mảng visited[u]
Return true;//Đồ thị liên thông mạnh
}
Ví dụ: Cho ma trận kề của đồ thị có hướng G= <V, E> Xác định xem G
có liên thông mạnh hay không?
1 2 3 4 5 6 7 8 9 10 11 12 13
Trang 115 0 0 0 0 0 0 1 0 0 0 0 0 0
Đỉnh u ∈V BFS(u) BFS(u) = V ?
1 1, 6, 10, 11, 2, 3, 8, 9, 13, 4, 12, 5, 7 YES
2 2, 3, 8, 9, 13, 4, 12 ,5, 7, 11, 1, 6, 10 YES
3 3, 9, 13, 5, 7, 11, 2, 8, 4, 12, 1, 6, 10 YES
4 4, 1, 6, 10, 12, 2, 3, 8, 9, 13, 5, 7, 11 YES
5 5, 7, 11, 13, 2, 8, 9, 3, 4, 12, 1, 6, 10 YES
6 6, 10, 12, 2, 3, 4, 8, 9, 13, 1, 5, 7, 11 YES
Trang 127 7, 11, 13, 2, 8, 9, 3, 4, 12, 5, 1, 6, 10 YES
8 8, 4, 12, 1, 6, 10, 2, 3, 9, 13, 5, 7, 11 YES
9 9, 5, 7, 11, 13, 2, 8, 3, 4, 12, 1, 6, 10 YES
10 10, 2, 3, 8, 9, 13, 4, 12, 5, 7, 11, 1, 6 YES
11 11, 2, 8, 3, 4, 12, 9, 13, 1, 6, 10, 5, 7 YES
12 12, 4, 10, 1, 6, 2, 3, 8, 9, 13, 5, 7, 11 YES
Trang 1313 13, 9, 11, 5, 7, 2, 8, 3, 4, 12, 1, 6, 10 YES
-Ta thấy cột ngoài cùng của bảng BFS(u) = V
thông mạnh
∀ u ∈V nên kết luận G là liên
ta kết luận đồ thị không liên
V Phần code ứng dụng của BFS để đếm số thành phần liên
thông của đồ thị và chỉ ra những thành phần liên thông
-Đề bài: Cho danh sách cạnh của đồ thị vô hướng G = <n, m > Xác định xem G có liên thông hay không và chỉ ra các thành phần liên thông của G
- Danh sách cạnh (input)
10 8 // 10 là số đỉnh, 8 là số cạnh
1 2
2 3
2 4
3 6
3 7
Trang 146 7
5 8
8 9
Code
#include<bits/stdc++.h>
using namespace std;
/ Duyệt tất cả các thành phần liên thông của đồ thị
int n, m; // n là số lượng đỉnh , m là số lượng cạnh
vector<int> adj[1001]; // vector adj để lưu danh sách kề của G = (n, m)
bool visited[1001]; // mảng visited dùng để đánh dấu các đỉnh đã
được thăm
void nhap(){
Trang 15memset(visited, false, sizeof(visited)); cin >> n >> m;
for(int i = 1; i <= m; i++){
int x, y; cin >> x >> y;
adj[x].push_back(y);
adj[y].push_back(x);
}
}
void BFS(int u){
queue<int> q;
q.push(u);
visited[u] = true;
while(!q.empty()){
int v = q.front();
Trang 16cout << v << ' ';
for(int x : adj[v]){
if(!visited[x]){
q.push(x);
visited[x] = true;
} }
}
}
void connectedComponent(){
int ans = 0; // Khởi tạo số TPLT của đồ thị for(int i = 1; i <= n; i++){
Trang 17++ans;
cout << "Cac dinh thuoc thanh phan lien thong thu " << ans << " :\n";
BFS(i);
cout << endl;
} }
if(ans == 1)
cout << "Do thi lien thong !\n";
else
cout << "Do thi khong lien thong !\n";
}
int main(int argc, char const *argv[])
Trang 18nhap();
connectedComponent();
return 0;
}
Out put:
Cac dinh thuoc thanh phan lien thong thu 1 : 123467
Cac dinh thuoc thanh phan lien thong thu 2 :
5 8 9
Cac dinh thuoc thanh phan lien thong thu 3 : 10
Do thi khong lien thong !