You are on page 1of 10

HỌ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ọ và tên Mã sinh viên


Đinh Tiến Hùng B20DCVT173
Lê Quang Anh B20DCVT021

Hà Nội 2022
I. 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,…

- 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.
While(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
STT Trạng thái Queue Các đỉnh được duyệt
1 1 Ø
2 2,3,5,10 1
3 3,4,5,10 1,2
4 5,10,4,6,7,9 1,2,3
5 10,4,6,7,9,8 1,2,3,5
6 4,6,7,9,8 1,2,3,5,10
7 6,7,9,8 1,2,3,5,10,4
8 7,9,8 1,2,3,5,10,4,6
9 9,8 1,2,3,5,10,4,6,7
10 8 1,2,3,5,10,4,6,7,9
11 Ø 1,2,3,5,10,4,6,7,9,8

III, Đánh giá độ phức tạp của thuật toán BFS


*)Độ 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).

IV. 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
if(!visited[i]){
++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
For 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
1 0 0 0 0 0 1 0 0 0 0 0 0 0
2 0 0 1 0 0 0 0 1 0 0 0 0 0
3 0 0 0 0 0 0 0 0 1 0 0 0 1
4 1 0 0 0 0 1 0 0 0 0 0 0 0
5 0 0 0 0 0 0 1 0 0 0 0 0 0
6 0 0 0 0 0 0 0 0 0 1 0 1 0
7 0 0 0 0 0 0 0 0 0 0 1 0 1
8 0 0 0 1 0 0 0 0 0 0 0 1 0
9 0 0 0 0 1 0 1 0 0 0 0 0 0
10 0 1 1 0 0 0 0 0 0 0 0 0 0
11 0 1 0 0 0 0 0 1 0 0 0 0 0
12 0 0 0 1 0 0 0 0 0 1 0 0 0
13 0 0 0 0 0 0 0 0 1 0 1 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
7 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
13 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 ∀ u ∈V nên kết luận G là liên
thông mạnh. Nếu 1 hàng nào đó có BFS(u) ≠ V ta kết luận đồ thị không liên
thông mạnh

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
12
23
24
36
37
67
58
89

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(){
memset(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();
q.pop();
cout << 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++){
if(!visited[i]){
++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[])


{
nhap();
connectedComponent();
return 0;
}

Out put:
Cac dinh thuoc thanh phan lien thong thu 1 :
123467
Cac dinh thuoc thanh phan lien thong thu 2 :
589
Cac dinh thuoc thanh phan lien thong thu 3 :
10
Do thi khong lien thong !

You might also like