chuyên đề lý thuyết đồ thị tìm kiếm theo chiều sâu Tư tưởng thuật toán có thể trình bày như sau: Từ một đỉnh S ban đầu ta sẽ có các đỉnh kề là A, từ đỉnh A ta sẽ có các đỉnh kề là D, và nó cũng thuộc nhánh SAD… Chúng ta thăm các nhánh đó theo chiều sâu (thăm đến khi không còn đỉnh kề chưa duyệt). Điều đó gợi cho chúng ta viết một thủ tục đệ quy DFS(u) để mô tả việc duyệt từ đỉnh u sang đỉnh kề v chưa được thăm.
Trang 1ỨNG DỤNG THUẬT TOÁN TÌM KIẾM CHIỀU SÂU DFS GIẢI
MỘT SỐ BÀI TẬP TIN HỌC
I Ý tưởng và cài đặt thuật toán DFS
Tư tưởng thuật toán có thể trình bày như sau: Từ một đỉnh S ban đầu ta sẽ có
các đỉnh kề là A, từ đỉnh A ta sẽ có các đỉnh kề là D, và nó cũng thuộc nhánh S-A-D… Chúng ta thăm các nhánh đó theo chiều sâu (thăm đến khi không còn đỉnh kề chưa duyệt) Điều đó gợi cho chúng ta viết một thủ tục đệ quy DFS(u) để mô tả việc duyệt từ đỉnh u sang đỉnh kề v chưa được thăm
1 Mô hình giải thuật DFS
Giải thuật DFS có thể viết theo mô hình dưới đây:
void dfs(int u)
{
free[u]=false; // đánh dấu đỉnh u đã được thăm
for (int v=1; v<=n; v++)
if ((tồn tại cạnh u, v) và (free[u][v]==true)) // tồn tại đỉnh kề với u, chưa được
Trang 2dfs(v); //duyệt đỉnh v
}
2 Ví dụ minh họa
Cho đồ thị vô hướng gồm n đỉnh, m cạnh, các thành phần trên đồ thị liên thông với
nhau Viết chương trình ghi ra thứ tự duyệt DFS xuất phát từ đỉnh s.
Dữ liệu vào:
Dòng đầu: gồm 3 số nguyên n, m, s, t (1<=n, m<=100, 1<=s<=n)
M dòng tiếp theo: mỗi dòng gồm 2 số u, v, mô tả 1 cạnh trong đồ thị
Dữ liệu ra:
Gồm nhiều dòng là thứ tự đường đi từ s
Ví dụ
7 7 1
1 2
1 3
1 5
2 4
2 6
3 7
5 6
1 2 4 6 5 3 7
Hướng dẫn:
- Sử dụng thuật toán tìm kiếm chiều sâu (DFS)
#include <iostream>
#include <bits/stdc++.h>
using namespace std;
int a[101][101];
Trang 3int n, m, Free[101], u, v, s;
void DFS(int u) {
cout <<u <<endl;
Free[u] = false;
for (int v = 1; v<=n; v++)
if (a[u][v] ==1 && Free[v]) DFS(v);
} int main() {
freopen ("DFS.inp", "r", stdin);
freopen ("DFS.out", "w", stdout);
cin >> n >> m >> s;
for (int i=1; i<=n; i++) for (int j=1; j<=n; j++) a[i][j] = 0;
for (int i=1; i<=m; i++) {
cin >> u >> v;
a[u][v] = 1;
a[v][u] = 1;
} for (int i =1; i<=n; i++)
Free[i] = 1;
DFS(s);
//cout <<u <<endl;
return 0;
}
3 Độ phức tạp DFS
Độ phức tạp thời gian: O(|V|+|E|) với |V| là số đỉnh của đồ thị, |E| là số cạnh
Trang 4II Bài tập ứng dụng thuật toán DFS
Bài 1: Bãi cỏ ngon nhất - VBGRASS
Bessie dự định cả ngày sẽ nhai cỏ xuân và ngắm nhìn cảnh xuân trên cánh đồng của nông dân John, cánh đồng này được chia thành các ô vuông nhỏ với R (1 <= R <= 100) hàng và C (1 <= C <= 100) cột Bessie ước gì có thể đếm được số khóm cỏ trên cánh đồng
Mỗi khóm cỏ trên bản đồ được đánh dấu bằng một ký tự ‘#‘ hoặc là 2 ký tự ‘#’ nằm kề nhau (trên đường chéo thì không phải) Cho bản đồ của cánh đồng, hãy nói cho Bessie biết có bao nhiêu khóm cỏ trên cánh đồng
Ví dụ như cánh đồng dưới dây với R=5 và C=6:
.#
#
# #
##
.#
Cánh đồng này có 5 khóm cỏ: một khóm ở hàng đầu tiên, một khóm tạo bởi hàng thứ 2 và thứ 3 ở cột thứ 2, một khóm là 1 ký tự nằm riêng rẽ ở hàng 3, một khóm tạo bởi cột thứ 4 và thứ 5 ở hàng 4, và một khóm cuối cùng ở hàng 5
Dữ liệu
Dòng 1: 2 số nguyên cách nhau bởi dấu cách: R và C
Dòng 2 R+1: Dòng i+1 mô tả hàng i của cánh đồng với C ký tự, các ký tự là ‘#’ hoặc ‘.’
Kết quả
Dòng 1: Một số nguyên cho biết số lượng khóm cỏ trên cánh đồng
Trang 5Ví dụ
.#
#
# #
##
.#
5
Hướng dẫn
#include <iostream>
#include <bits/stdc++.h>
using namespace std;
int n, m;
int s=0;
char a[101][101];
bool Free[101][101];
int td[4] = {0, 0, -1, 1};
int tc[4] = {-1, 1, 0, 0};
void DFS (int u, int v)
{
int u1, v1;
Free[u][v] = true;
for (int i=1; i<=4; i++)
{
u1 = u + td[i];
v1 = v + tc[i];
if ((a[u1][v1] == '#') && (!Free[u1][v1] ))
DFS(u1,v1);
Trang 6}
}
int main()
{
freopen("vbgrass.inp", "r", stdin);
freopen("vbgrass.out", "w", stdout);
cin >> m >> n;
for (int i=1; i<=m; i++)
for (int j=1; j<=n; j++)
cin >> a[i][j];
for (int i=1; i<=m; i++)
for (int j=1; j<=n; j++)
if ((a[i][j] == '#')&& (!Free[i][j]))
{
DFS(i,j);
s++;
}
cout << s;
}
Bài 2: ZEROPATH - Phân tích số
Mỗi một số nguyên dương đều có thể biểu diễn dưới dạng tích của 2 số nguyên dương X,Y sao cho X<=Y Nếu như trong phân tích này ta thay X bởi X-1 còn Y bởi Y+1 thì sau khi tính tích của chúng ta thu được hoặc là một số nguyên dương mới hoặc là
số 0
Ví Dụ : Số 12 có 3 cách phân tích 1*12 ,3*4 , 2*6 Cách phân tích thứ nhất cho ta tích mới là 0 : (1-1)*(12+1) = 0 , cách phân tích thứ hai cho ta tích mới 10 : (3-1)*(4+1)
Trang 7= 10 , còn cách phân tích thứ ba cho ta 7 : (2-1)*(6+1)=7 Nếu kết quả là khác 0 ta lại lặp lại thủ tục này đối với số thu được Rõ ràng áp dụng liên tiếp thủ tục trên , cuối cùng
ta sẽ đến được số 0 , không phụ thuộc vào việc ta chọn cách phân tích nào để tiếp tục
Yêu cầu : Cho trước số nguyên dương N ( 1<=N<=100000) , hãy đưa ra tất cả các số
nguyên dương khác nhau có thể gặp trong việc áp dụng thủ tục đã mô tả đối với N
Dữ liệu : 1 dòng chứa số nguyên dương N
Kết quả : Gồm 2 dòng
Dòng đầu tiên ghi K là số lượng số tìm được
Dòng tiếp theo chứa K số tìm được theo thứ tự tăng dần bắt đầu từ số 0
Lưu ý : Có thể có số xuất hiện trên nhiều đường biến đổi khác nhau , nhưng nó chỉ
được tính một lần trong kết quả
0 3 4 6 7 10
Hướng dẫn:
- Dùng thuật toán tìm kiếm theo chiều sâu
#include <iostream>
#include <bits/stdc++.h>
using namespace std;
int n, d[100000]={0};
void DFS(int n)
{
for (int v=1; v <= sqrt(n); v++)
{
if (n%v == 0)
{
int x = v-1;
int y = n/v + 1;
Trang 8if (d[x*y] == 0)
{
d[x*y]=1;
DFS(x*y);
}
}
}
}
int main()
{
freopen("zeropath.inp", "r", stdin);
freopen("zeropath.out", "w", stdout);
int dem=0;
cin >> n;
DFS(n);
for (int i=0; i <=n; i++)
if (d[i] ==1) dem++;
cout <<dem<< endl;
for (int i=0; i<=n; i++)
if (d[i] ==1) cout <<i <<" ";
}
Bài 3: ADS spoj – Quảng cáo
Nhân dịp Tết sắp đến công ty Jelly-for-Kids quyết định tăng cường việc quảng bá sản phẩm đến người tiêu dùng Vì thế giám đốc marketing, ông Fruit-Jelly muốn gửi đi số lượng nhân viên tối đa có thể, làm nhiệm vụ tiếp thị tại đại lý trong thành phố
Trong thành phố có m con đường, n đại lý bán kẹo (đánh số từ 1 đến n) Mỗi con đường chỉ nối trực tiếp giữa 2 đại lý, và được ký hiệu bằng chỉ số của 2 đại lý mà nó nối Đồng thời, giữa 2 đại lý bất kỳ có không quá 1 con đường nối chúng
Trang 9Ông Fruit-Jelly nghĩ rằng, ông ta sẽ quản lý nhân viên dễ hơn nếu xếp mỗi người tiếp thị trên những hành trình có tính chất thứ tự Tức là những đại lý bán kẹo trên hành trình đó thỏa các điều kiện sau
Có đường nối trực tiếp giữa 2 đại lý liên tiếp nhau trên hành trình
Từ một đại lý bất kỳ trong hành trình có thể đi qua tất cả các đoạn đường trong hành trình đó rồi trở về nơi xuất phát mà không đi qua đoạn đường nào quá một lần
Hành trình phân công cho mỗi nhân viên phải có ít nhất một đoạn đường chưa có nhân viên nào khác đi tiếp thị
Mỗi nhân viên chỉ di chuyển trên hành trình mà anh ta được phân công Hãy tính
số lượng nhân viên tối đa mà ông Fruit-Jelly có thể xếp việc, và hành trình cụ thể mà mỗi người được xếp
Input
Dòng đầu là 2 số tự nhiên N và M (N<=2000) (M<=5000)
Trong M dòng tiếp theo, mỗi dòng ghi 2 số nguyên mô tả một đoạn đường, mỗi đoạn đường được mô tả bởi chỉ số của 2 đại lý mà nó nối
Output
Dòng đầu tiên ghi Q là số lượng nhân viên tối đa tìm được
5 6
1 2
2 4
4 5
3 5
1 3
2 3
2
Hướng dẫn:
- Dùng thuật toán tìm kiếm theo chiều sâu
Trang 10#include <iostream>
#include <bits/stdc++.h>
using namespace std;
int n,m, res, dem, x, y;
int a[2000][2000];
bool Free[2000];
void DFS(int i)
{
Free[i]=true;
for (int j=1; j<=n; j++)
{
if ((a[i][j]==1) && (!Free[j]))
DFS(j);
}
}
int main()
{
freopen("ADS.inp" , "r", stdin);
freopen("ADS.out", "w", stdout);
cin >> n >> m;
for (int i=1; i<=m; i++)
{
cin >> x >> y;
a[x][y] =1;
a[y][x] =1;
}
for (int i=1; i<=n; i++)
if (!Free[i])
Trang 11{
dem++;
DFS(i);
}
res = m - n + dem;
cout << res;
}
III Bài tập tự làm
Bài 1: DS – ebola
Một cơ quan có n nhân viên được đánh số thứ tự từ 1 đến n Mỗi người có một phòng làm việc riêng của mình Do nhu cầu công việc, hàng ngày mỗi nhân viên có thể phải tiếp xúc với một số nhân viên khác Vào một ngày làm việc bình thường, có một nhân viên bị nhiễm bệnh Ebola, nhưng do không biết nên người này vẫn đi làm Đến cuối ngày làm việc người ta mới phát hiện ra người nhiễm bệnh Ebola đầu tiên, Khả năng lây lan của Ebola rất nhanh chóng: một người nhiễm bệnh nếu tiếp xúc với một người khác
có thể sẽ truyền bệnh cho người này
Yêu cầu: Hãy giúp các bác sĩ kiểm tra xem cuối ngày hôm đó, có tối đa bao nhiêu người
có thể sẽ nhiễm bệnh và đó là những người nào để còn cách ly Người có tiếp xúc với người nhiễm bệnh được coi là người nhiễm bệnh
Dòng đầu tiên ghi 2 số tự nhiên n,k (1<=n<=10^5, 1<=k<=n) tương ứng là số lượng người làm việc trong toà nhà và số hiệu của nhân viên đã nhiễm Ebola đầu tiên
Dòng thứ i trong n dòng tiếp theo ghi danh sách những người có tiếp xúc với người thứ i theo cách sau: số đầu tiên m của dòng là tổng số nhân viên đã gặp người thứ i, tiếp theo là m số tự nhiên lần lượt là số hiệu của các nhân viên đó Nếu m=0 có nghĩa rằng không ai đã tiếp xúc với người thứ i
Dữ liệu được cho đảm bảo tổng số lần tiếp xúc của tất cả nhân viên trong cơ quan không vượt quá 10^6
Trang 12 Dòng thứ 2 liệt kê tất cả nhân viên có thể bị lây nhiễm Ebola cần cách ly, danh sách cần được sắp theo thứ tự tăng dần của số hiệu nhân viên
5 1
2 2 3
2 1 3
2 1 2
1 5
1 4
3
1 2 3
Bài 2: Đếm số ao - BCLKCOUN
Sau khi kết thúc OLP Tin Học SV, một số OLP-er quyết định đầu tư thuê đất để trồng rau
Mảnh đất thuê là một hình chữ nhật N x M (1<=N<=100; 1<=M<=100) ô đất hình vuông Nhưng chỉ sau đó vài ngày, trận lụt khủng khiếp đã diễn ra làm một số ô đất bị ngập :(( Mảnh đất biến thành một số các ao
Các OLP-er quyết định chuyển sang nuôi cá Vấn đề lại nảy sinh, các OLP-er muốn biết mảnh đất chia thành bao nhiêu cái ao để có thể tính toánnuôi trồng hợp lý Bạn hãy giúp các bạn ý nhé
Chú ý: Ao là gồm một số ô đất bị ngập có chung đỉnh Dễ nhận thấy là một ô đất
có thể có tối đa 8 ô chung đỉnh
INPUT:
* Dòng1: 2 số nguyên cách nhau bởi dấu cách: N và M
* Dòng 2 N+1: M kí tự liên tiếp nhau mỗi dòng đại diện cho 1 hàng các ô đất
Mỗi kí tự là ‘W’ hoặc ‘.’ tương ứng với ô đất đã bị ngập và ô đất vẫn còn nguyên
OUTPUT:
* Dòng 1: 1 dòng chứa 1 số nguyên duy nhất là số ao tạo thành
Trang 13Input Output
10 12
W…… WW
.WWW… WWW
….WW…WW
………WW
………W
W……W
.W.W… WW
W.W.W… W
.W.W……W
W…….W
3