Download as docx, pdf, or txt
Download as docx, pdf, or txt
You are on page 1of 10

2 – BFS:

BFS là viết tắt của Breadth-First Search (Tìm kiếm theo chiều rộng) là một dạng thuật toán tìm kiếm
trên đồ thị ưu tiên những đỉnh nào gần đỉnh xuất phát hơn sẽ 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ố hoặc 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ể.
Tư tưởng:
Việc duyệt các đỉnh trong BFS giống như vết dầu loang ra trên mặt phẳng, sẽ loang từ nơi gần đền
những chỗ xa hơn.
Ta có thể mô tả thuật toán như sau:
+ Đầu tiên ta thăm đỉnh nguồn S.
+ Việc thăm đỉnh S sẽ phát sinh thứ tự thăm các đỉnh (u1,u2,…uk) kề với S(những đỉnh gần S nhất).
+ Tiếp theo, ta thăm đỉnh u1, khi thăm đỉnh u1 sẽ lại phát sinh yêu cầu thăm những đỉnh (v1,v2,
…,vp) kề với u1. Nhưng rõ ràng những đỉnh v này “xa” S hơn những đỉnh u nên chúng chỉ được thăm
khi tất cả những đỉnh u đều đã được thăm. Tức là thứ tự thăm các đỉnh sẽ là: s,u1,u2,…,uk,v1,v2,
…,vp,…
Từ đây, ta thấy để duy trình danh sách các đỉnh được thăm theo thứ tự trên, ta sẽ sử dụng 1 danh sách
để lưu các đỉnh, với đỉnh nào vào trước sẽ được thăm trước, vào sau sẽ được thăm sau. Đó chính là
cấu trúc dữ liệu queue đã trình bày ở quyển 2.
Bài toán : Cho đồ thị vô hướng không trọng số và đỉnh S thuộc đồ thị. In ra khoảng cách ngắn nhất từ
S đến tất cả các đỉnh.
Dữ liệu: Nhập từ file BFS.INP:
- Dòng đầu tiên gồm 3 số N là số đỉnh của đồ thị, M là số cạnh của đồ thị và đỉnh S (N, M <=
100).
- M dòng tiếp theo, mỗi dòng có dạng 2 số nguyên dương u và v thể hiện có đường nối từ 2 đỉnh
u và v.
Kết quả: Ghi ra file BFS.OUT:
- N dòng, dòng thứ i là số thể hiện khoảng cách ngắn nhất từ S đến đỉnh i.
Ví dụ:
BFS.INP BFS.OUT
10 10 1 0
13 1
17 1
12 2
35 2
34 2
4 10 1
2 10 3
26 3
59 2
68

Hướng dẫn:
Thuật toán có thể trình bày như sau : ta sẽ tạo 1 queue<int> q để lưu thứ tự các đỉnh được duyệt.
Mảng d[u] là khoảng cách ngắn nhất từ S đến u. Ban đầu ta sẽ đẩy đỉnh S vào trong q. Tiếp theo, ta
duyệt các phần tử trong q bằng cách sử dụng vòng lặp while, và với đỉnh u đang được duyệt, lấy đỉnh
u ra khỏi q, với các đỉnh v kề với nó, nếu v chưa được thăm, ta sẽ gán d[v] = d[u] + 1 sau đó đẩy v
vào hàng đợi và cứ tiếp tục cho tới khi hết.

Mã giả:
void bfs() {
đẩy S vào q
trong khi q khác rỗng {
lấy ra đỉnh u ở đầu queue;
đánh dấu u đã được thăm;
với các đỉnh v kề với u {
nếu v chưa được thăm {
d[v] = d[u] + 1;
đẩy v vào queue;
}
}
}
}
Ví dụ:
Từ đồ thị đã cho ở trên:
Khởi tạo ban đầu d[x] = + (trong trường hợp có n đỉnh thì + = n là được. Lý do: trường hợp có
đường đi, kết quả lớn nhất chỉ có thể là n -1); Ta lợi dụng luôn mảng d[] để đánh dấu luôn những đỉnh
đã thăm. Ta có nhận xét khi d[t] = + nghĩa là t chưa được thăm. Ngược lại d[t] < + nghĩa là t đã
được thăm rồi. Ban đầu, ta đẩy đỉnh xuất phát S vào trong queue (trong trường hợp này là đỉnh 1
chẳng hạn), và gán d[1] = 0.
Tiếp theo, ta sẽ lấy đỉnh 1 ra khỏi queue, đi đến các đỉnh kề với 1 là các đỉnh 3, 7, 2; đẩy lần lượt vào
queue và gán lại d[3] = d[7] = d[2] = d[1] + 1 = 1.
Trong queue lúc này có giá trị là {3, 7, 2}.
Tiếp theo, ta lấy đỉnh 3 ra khỏi queue, các đỉnh kề với 3 là đỉnh 4, 5 và 1.
 Đỉnh 1 đã được thăm (do d[1] = 0), do đó ta sẽ không đưa đỉnh 1 vào xét tiếp (vì xét cũng bằng
thừa).
 d[5]= d[4]= d[3]+1= 2, ta đẩy tiếp đỉnh 5 và 4 vào queue: {7, 2, 5, 4}.
Tương tự với đỉnh 2 và 7, ta có: d[10] = d[6] = 2, queue: {5, 4, 10, 6}.
Tiếp theo, lấy đỉnh 5 ra khỏi queue: d[9]=d[5]+1=3, queue: {4, 10, 6, 9}.
Với đỉnh 4, kề với 2 đỉnh là 3 và 10. Nhưng ta đều thấy d[3] và d[10] < d[4] + 1 nên ta không cần phải
đưa 3 và 10 vào lại trong queue (hay nói cách khác là đỉnh 3 và 10 đã được xét trước đó rồi). Điều
tương tự xảy ra với đỉnh 10.
Với đỉnh 6, ta cũng làm tương tự và d[8] = d[6] + 1 = 3. Queue: {} (rỗng).
Khi queue rỗng, ta kết thúc BFS.
Lúc này ta được: d[1] = 0, d[3] = d[7] = d[2] = 1, d[5] = d[4] = d[10] = d[6] = 2, d[9] = d[8] = 3,
chính là khoảng cách từ đỉnh xuất phát 1 đến các đỉnh đó.
Độ phức tạp của thuật toán này là O(N + M).
Chương trình:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define X first
#define Y second
const int N = 107;
const int inf = 1e9 + 7;
int n, m, s, d[N];
vector <int> adj[N];
void bfs() {
queue <int> q;
q.push(s); // đẩy s vào queue.
while (! q.empty()) { // trong khi queue khác rỗng.
int u = q.front();
q.pop(); // lấy đỉnh đầu tiên ra khỏi queue.
for (int i = 0; i < adj[u].size(); i++) {
int v = adj[u][i]; // v kề với u.
if (d[v] == inf) { // v chưa được thăm
d[v] = d[u] + 1;
if(v==t) return;
q.push(v); // đẩy v vào queue để thăm.
}
}
}
}
int main()
{
ios_base::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
freopen("bfs.inp","r", stdin);
freopen("bfs.out", "w", stdout);
cin >> n >> m >> s;
for (int i = 1;i <= n; i++) {
int u, v;
cin >> u >> v;
adj[u].push_back(v); // lưu danh sách kề.
adj[v].push_back(u);
}
for (int i = 1;i <= n; i++)
d[i] = inf; // khởi tạo mảng d[].
d[s] = 0;
bfs();
for (int i = 1;i <= n; i++) {
cout << d[i] << '\n';
}
return 0;
}

Bài tập áp dụng


Câu 1: MECUNG.CPP

Mê cung hình chữ nhật kích thước m*n gồm các ô vuông đơn vị (m, n ≤ 103). Trên mỗi ô ghi một
trong ba kí tự:

+ O: Nếu ô đó an toàn

+ X: Nếu ô đó có cạm bẫy


+ E: Nếu là ô có một nhà thám hiểm đang đứng

Duy nhất chỉ có 1 ô ghi chữ E. Nhà thám hiểm có thể từ một ô đi sang một trong số các ô chung
cạnh với ô đang đứng. Một cách đi thoát khỏi mê cung là một hành trình đi qua các ô an toàn ra một ô
biên. Hãy chỉ giúp cho nhà thám hiểm một hành trình thoát ra khỏi mê cung đi qua ít ô nhất kể cả ô
xuất phát.

Dữ liệu: Vào từ file văn bản MECUNG.INP gồm

+ Dòng đầu là 2 số nguyên dương m, n

+ m dòng tiếp theo mỗi dòng là n ký tự O, X, E

Kết quả: Ghi ra file MECUNG.OUT là kết của bài toán. Nếu không có cách thoát hiểm thì in ra -1

MECUNG.INP MECUNG.OUT

56 6
XXXOOX
OXXXXX
XOOOOX
OOOXEX
OXOXXX

Câu 2: MWALKING.CPP
Vùng đất Belste được xem như một bản đồ kích thước N x N với mỗi ô là độ cao của nó
so với mặt nước biển. Trong một ngày đi khám phá Belste, Vinci muốn tìm hiểu một
cách di chuyển sao cho độ chênh lệch giữa ô cao nhất và thấp nhất trên đường đi là nhỏ
nhất. Biết rằng anh ấy đang ở ô (1,1) và muốn đến ô (N,N) và chỉ được đi sang trái, phải,
lên trên và xuống dưới chứ không thể đi chéo. Các bạn hãy cùng nhau giúp Vinci nhé.
Dữ liệu: Nhập vào từ file MWALKING.INP:
+ Dòng đầu tiên là số N (2 ≤ N ≤ 100).
+ Tiếp theo là ma trận A có kích thước NxN là độ cao của vùng đất(0 ≤ Aij ≤ 100).
Kết quả: Ghi ra file MWALKING.OUT:
+ Dòng duy nhất là chênh lệch độ cao nhỏ nhất.
Ví dụ:
MWALKING.INP MWALKING.OUT
5 2
11368
12255
44033
80234
43021

Câu 3: GRASS.CPP
Chú bò Gogh rất yêu bãi cỏ của mình và rất hay dạo chơi trên đó.
Gogh đã chia đồng cỏ của mình là 1 vùng hình chữ nhật thành các ô vuông nhỏ với N (1≤ N
≤100) hàng và M (1≤ M ≤100) cột, đồng thời đánh dấu chỗ nào là cỏ và chỗ nào là đá.
Mỗi buổi tối, Gogh lại được gọi về để vắt sữa nên Gogh cần chạy về chuồng nhanh nhất có thể. Tuy
vậy, vì rất yêu cỏ nên Gogh muốn trên đường chạy về đi qua nhiều vùng cỏ nhất có thể.
Ban đầu, Gogh ở vị trí X,Y và khi được gọi, sẽ trở về chuồng ở ô 1, 1. Chú bò có thể đi từ 1 ô vuông
sang 4 ô vuông khác kề cạnh nhưng không được đi vào ô có đá hay đi ra khỏi đồng cỏ.
Hãy tính xem Gogh đi qua được nhiều nhất bao nhiêu vùng cỏ biết trong chuồng thì không có cỏ.
Dưới đây là một bản đồ ví dụ (với đá (' * '), cỏ (' . '), chuồng bò ('B'), và Gogh ('C') ở hàng 5, cột 6) và
một bản đồ cho biết hành trình tối ưu của Gogh, đường đi được dánh dấu bằng chữ 'm'.
Bản đồ Đường đi tối ưu

Dữ liệu: Nhập từ file GRASS.INP:


+ Dòng đầu tiên gồm 2 số nguyên N và M là kích thước bãi cỏ.
+ Tiếp theo là ma trận RxC thể hiện bãi cỏ.
Kết quả : Ghi ra file GRASS.OUT:
+ Dòng duy nhất là số ô cỏ mà Gogh đi qua.
Ví dụ:
GRASS.INP GRASS.OUT
56 9
B...*.
..*...
.**.*.
..***.
*..*.C

Câu 4: PCONNECT.CPP
Trên mặt phẳng toạ độ xét các điểm có toạ độ nguyên. Thầy PHUND cho N điểm màu đỏ và bắt đầu
trò chơi như sau: với 2 điểm màu đỏ cùng dòng hay cùng cột, nối 2 điểm đó bằng 1 đoạn thẳng, các
điểm có toạ độ nguyên nằm trên đoạn thẳng đó sẽ được tô đỏ.
Thầy sẽ làm như vậy cho đến khi không có thêm điểm đỏ nào nữa. Sau đó, thầy đố các bạn học sinh
đội tuyển Tin thử tính xem có bao nhiêu điểm đỏ khi kết thúc trò chơi. Hai điểm được xem là khác
nhau nếu có tọa độ khác nhau. Các bạn hãy lập trình để giải bài toán này nhé.
Yêu cầu: Nhập từ file PCONNECT.INP:
+ Dòng đầu tiên chứa số nguyên dương N là số điểm đỏ ban đầu (1 ≤ N ≤ 105).
+ N dòng tiếp theo là tọa độ (x, y) của các điểm, (1 ≤ |x|, |y| ≤ 103). Các tọa độ được cho có thể trùng
nhau.
Kết quả: Ghi ra file PCONNECT.OUT:
+ 1 dòng duy nhất là số điểm đỏ cuối cùng.
Ví dụ:
PCONNECT.INP PCONNECT.OUT
4 12
02
31
14
44

Câu 5: EMAZE.CPP
Picasso sau khi bán được bức tranh “Dora Maar” đã thu được rất nhiều tiền. Ông đã quyết định xây
một mê cung cho riêng mình với N phòng và N – 1 hành lang. Từ phòng thứ u có thể đến được phòng
v bất kì bằng cách đi qua một vài hành lang và mỗi hành lang sẽ mất 1 phút.
Sau khi xây xong, Picasso đã mời K họa sĩ là học trò của mình đến để chơi 1 trò chơi như sau. Ông
bắt đầu ở vị trí phòng 1, và nếu có thể đi đến 1 phòng nào đó khác 1 và phòng đó chỉ có đúng 1 hành
lang đi vào, ông sẽ dành chiến thắng.
Tuy nhiên, học trò của ông cũng rất ranh mãnh khi đứng ở các phòng, và nếu họ nhìn thấy Picasso
trước khi ông chiến thắng, ông sẽ thua.
Tuy nhiên, sau khi chơi một số lần và Picasso toàn thắng, học trò của ông lại quyết định thay đổi cách
chơi. Cứ 1 phút, học trò sẽ đi qua 1 hàng lang và đến phòng liền kề. Tất cả học trò phải cùng di
chuyển 1 lúc hoặc cùng không di chuyển.
Liệu rằng có cách đi nào mà cho dù học trò của Picasso di chuyển như thế thì ông vẫn dành chiến
thắng hay không ? Hãy trả lời giúp ông nhé.
Dữ liệu: Nhập từ file EMAZE.INP:
+ Dòng đầu tiên gồm 2 số N và K (1 ≤ K < N ≤ 2*105).
+ Dòng tiếp theo gồm K số x[] với ý nghĩa x[i] là vị trí phòng ban đầu của học trò thứ i.
+ N – 1 dòng tiếp theo gồm 2 số u, v thể hiện tồn tại hành lang đi từ phòng u đến v.
Kết quả: Ghi ra file EMAZE.OUT:
+ 1 dòng duy nhất in ra “YES” nếu Picasso có thể thắng, “NO” nếu Picasso thua.
Ví dụ:
EMAZE.INP EMAZE.OUT
82 YES
53
47
25
16
36
72
17
68
Giải thích: Chỉ cần đi 1 – 7 – 4 , Picasso sẽ chiến thắng.

Câu 6 PIGPEN.CPP
VanhG sau khi trúng xổ số đã quyết định về quê nuôi lợn và trồng thêm rau. Chuồng lợn của anh ấy là
một hình chữ nhật kích thước NxM, mỗi ô là ô trống hoặc chứa vật cản. Đầu tiên, anh đặt 1 chú lợn
vào trong chuồng và nhận thấy chú lợn này có xu hướng di chuyển một số bước rồi lại quay về chỗ cũ
như một chu trình với độ dài là K. Đột nhiên, VanhG viết lại các bước di chuyển của chú lợn như ‘L’
là đi sang ô liền trái, ‘R’ là đi sang ô liền phải, ‘U’ là đi lên ô liền trên, ‘D’ là đi xuống ô liền dưới.
Sau khi viết rất nhiều đường đi như vậy, VanhG tự thắc mắc xem đường đi nào có thứ tự từ điển nhỏ
nhất.
Các bạn hãy giúp VanhG nhé.
Chú ý : Chú lợn có thể đi qua 1 ô nhiều lần và ô xuất phát luôn là ô trống.
Dữ liệu: Nhập vào từ file PIGPEN.INP:
+ Dòng đầu tiên gồm 3 số N, M, K. (1 ≤ N, M ≤ 1000, 1 ≤ K ≤ 106).
+ Tiếp theo là ma trận NxM, ô kí hiệu là ‘.’ là ô trống, ô có kí hiệu là ‘*’ là ô có vật cản, và ô có dấu
‘X’ là vị trí ban đầu của chú lợn.
Kết quả: Ghi ra file PIGPEN.OUT:
+ Nếu tồn tại cách di chuyển, in ra thứ tự từ điển nhỏ nhất của các di chuyển đó
+ Nếu không tồn tại cách di chuyển, in ra “IMPOSSIBLE”.
Ví dụ:
PIGPEN.INP PIGPEN.OUT
232 RL
.**
X..
334 IMPOSSIBLE
***
*X*
***

Bổ sung thêm: http://vnspoj.blogspot.com/p/blog-page_41.html

You might also like