搜索题目在常见的算法竞赛和笔试面试中很常见,尤其是蓝桥杯中,很多题都能用暴搜去找答案。
在生产上也广泛用于拓扑排序,寻路(走迷宫),搜索引擎,爬虫等,也频繁出现在高频面试题中。
实现方法 | 基本思想 | 解决问题 | N规模 | |
---|---|---|---|---|
DFS | 栈/递归 | 回溯法,一次访问一条路,更接近人的思维方式, | 所有解问题,或连通性问题 | 不能太大,<=200 |
BFS | 队列 | 分治限界法,一次访问多条路,每一层需要存储大量信息 | 最优解问题,如最短路径 | 可以比较大,因为可以用队列解决,<=1000 |
DFS:可以使用 stack,不过多数情况下使用递归。
牢记:每一个 DFS 都会对应一个搜索树,如:n 皇后问题。
BFS:一般使用 queue,也可以用数组模拟队列。
const int MAXN = 10;
int n;
int ans[MAXN];
// vis用来标记本轮dfs是否已经用过
bool vis[MAXN];
// 用u来记录答案ans中的数量
void dfs(int u) {
// 如果u>n 说明ans中符合的答案已经存放完毕
if(u > n) {
for(int i = 1; i <= n; i++) {
if(i != 1) {
cout << " ";
}
cout << ans[i];
}
cout << endl;
return;
}
// 每一次for对应一颗搜索树
for(int i = 1; i <= n; i++) {
if(!vis[i]) {
vis[i] = 1;
ans[u] = i;
dfs(u+1);
vis[i] = 0;
}
}
}
int main() {
cin >> n;
dfs(1);
return 0;
}
const int MAXN = 20;
int n;
bool dg[MAXN], udg[MAXN], col[MAXN], row[MAXN];
char g[MAXN][MAXN];
/*
重点在于关于行、列、对角线上的判断
*/
// x和y是横纵坐标 s是皇后个数
void dfs(int x, int y, int s) {
// 列超界
if(y == n) {
y = 0, x++;
}
// 行超界 说明已经遍历完了所有格子
if(x == n) {
// 判断皇后总数是否已经够
if(s == n) {
for(int i = 0; i < n; i++) {
puts(g[i]);
}
puts("");
}
return;
}
// 不放皇后
dfs(x, y + 1, s);
// 放皇后
if(!row[x] && !col[y] && !dg[y-x+n] && !udg[y+x]) {
g[x][y] = 'Q';
row[x] = col[y] = dg[y-x+n] = udg[y+x] = true;
dfs(x, y + 1, s + 1);
row[x] = col[y] = dg[y-x+n] = udg[y+x] = false;
g[x][y] = '.';
}
}
int main() {
cin >> n;
for(int i = 0; i < n; i++) {
for(int j = 0; j < n; j++) {
g[i][j] = '.';
}
}
dfs(0, 0, 0);
return 0;
}
求连通块:经典DFS
首先,需要判断有没有不合法的船,即非矩形的。(如果一个块的右边、下边、右下边 四个块中有三个块是船,那么就是不合法的,一定无法构成矩形;否则可以构成矩形)
都合法之后,我们只需要求连通块的数量就行了:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define inf 0x3f3f3f3f
using namespace std;
typedef long long LL;
const int MAXN = 1e3+10;
int n, m;
char mapp[MAXN][MAXN];
int ans = 0;
int dx[] = {1, 0, -1, 0}, dy[] = {0, 1, 0, -1};
void dfs(int x, int y) {
// 先对这个点打标记 因为以后一定不会再访问了
mapp[x][y] = '.';
for(int i = 0; i < 4; i++) {
int a = x + dx[i], b = y + dy[i];
// 找到周围四个可访问的点进行深搜
if(a >= 0 && a < n && b >= 0 && b < m && mapp[a][b] == '#') {
dfs(a, b);
}
}
return;
}
bool check() { //判断是否不成长方形
int temp = 0;
// i 0~n-1 j 0~m-1
for(int i = 0; i < n - 1; i++)
for(int j = 0; j < m - 1; j++) {
temp = 0;
if(mapp[i][j] == '#')
temp++;
if(mapp[i+1][j] == '#')
temp++;
if(mapp[i][j+1] == '#')
temp++;
if(mapp[i+1][j+1] == '#')
temp++;
if(temp == 3) //如果为3则无法构成
return false;
}
return true;
}
int main() {
cin >> n >> m;
for(int i = 0; i < n; i++) {
cin >> mapp[i];
}
// 对地图上所有的2x2正方形块进行检查
if(!check()) {
printf("Bad placement."); //输出再结束
return 0;
}
for(int i = 0; i < n; i++) {
for(int j = 0; j < m; j++) {
if(mapp[i][j] == '#') {
dfs(i, j);
ans++;
}
}
}
printf("There are %d ships.", ans);
return 0;
}
这是一个裸的DFS数连通块问题,和上一个题的遍历方法完全一样。每个点可以向周围四个同样的点搜索。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define inf 0x3f3f3f3f
using namespace std;
typedef long long LL;
const int MAXN = 1e2+10;
int n, m;
char mapp[MAXN][MAXN];
int ans = 0;
int dx[] = {1, 0, -1, 0, 1, 1, -1, -1}, dy[] = {0, 1, 0, -1, 1, -1, 1, -1};
void dfs(int x, int y) {
mapp[x][y] = '.';
for(int i = 0; i < 8; i++) {
int a = x + dx[i], b = y + dy[i];
if(a >= 0 && a < n && b >= 0 && b < m && mapp[a][b] == 'W') {
dfs(a, b);
}
}
return;
}
int main() {
cin >> n >> m;
for(int i = 0; i < n; i++) {
cin >> mapp[i];
}
for(int i = 0; i < n; i++) {
for(int j = 0; j < m; j++) {
if(mapp[i][j] == 'W') {
dfs(i, j);
ans++;
}
}
}
cout << ans;
return 0;
}
裸连通块问题,现在发现写这个都是有点浪费时间了。回顾一下做此题时:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define inf 0x3f3f3f3f
using namespace std;
typedef long long LL;
const int MAXN = 1e2+10;
int n, m;
char mapp[MAXN][MAXN];
int ans = 0;
int dx[] = {1, 0, -1, 0}, dy[] = {0, 1, 0, -1};
void dfs(int x, int y) {
mapp[x][y] = '0';
for(int i = 0; i < 4; i++) {
int a = x + dx[i], b = y + dy[i];
if(a >= 0 && a < n && b >= 0 && b < m && mapp[a][b] != '0') {
dfs(a, b);
}
}
return;
}
int main() {
cin >> n >> m;
for(int i = 0; i < n; i++) {
cin >> mapp[i];
}
for(int i = 0; i < n; i++) {
for(int j = 0; j < m; j++) {
if(mapp[i][j] != '0') {
dfs(i, j);
ans++;
}
}
}
cout << ans << endl;
return 0;
}
经典的DFS求全排列问题。输入n,输出从1~n组成的所有不重复的数字序列,每行一个序列。
注意:在DFS中,从第一个数到最后一个数遍历,接下来访问没访问过的数,一直到数量超过n,将存答案的数组输出。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define inf 0x3f3f3f3f
using namespace std;
typedef long long LL;
const int MAXN = 11;
int n;
bool vis[MAXN];
int ans[MAXN];
void dfs(int u) {
if(u > n) {
for(int i = 1; i <= n; i++) {
printf("%5d", ans[i]);
}
puts("");
return;
}
for(int i = 1; i <= n; i++) {
if(!vis[i]) {
vis[i] = 1;
ans[u] = i;
dfs(u+1);
vis[i] = 0;
}
}
}
int main() {
cin >> n;
dfs(1);
return 0;
}
好家伙,最坑爹的题目、没有之一。本来以为就按照要求DFS呗,每次更新一下当前的最大值,最后在vis数组的置0置1问题上搞了半天。
仔细想,本题是要选取若干个不互相干扰的点,求它们和的最大值。因为是搜索,并且没有最短路的性质,所以我们用DFS。而且题目的数据范围一眼望去就很小,~嗯,是个深搜的好题~。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define inf 0x3f3f3f3f
using namespace std;
typedef long long LL;
const int MAXN = 8;
int T, n, m;
int mapp[MAXN][MAXN];
int vis[MAXN][MAXN];
int dx[] = {1, 0, -1, 0, 1, 1, -1, -1};
int dy[] = {0, 1, 0, -1, 1, -1, 1, -1};
int ans;
/*
确定算法和考虑标记状态
*/
int sum;
void dfs(int x, int y) {
if(y == m) {
y = 0, x++;
}
if(x == n) {
ans = max(ans, sum);
return;
}
// 不选
dfs(x, y + 1);
// 选
if(vis[x][y] == 0) {
for(int i = 0; i < 8; i++) {
int a = x + dx[i], b = y + dy[i];
if(a >= 0 && a < n && b >= 0 && b < m) {
vis[a][b]++;
}
}
sum += mapp[x][y];
dfs(x, y + 1);
for(int i = 0; i < 8; i++) {
int a = x + dx[i], b = y + dy[i];
if(a >= 0 && a < n && b >= 0 && b < m) {
vis[a][b]--;
}
}
sum -= mapp[x][y];
}
}
int main() {
cin >> T;
while(T--) {
ans = 0;
cin >> n >> m;
for(int i = 0; i < n; i++) {
for(int j = 0; j < m; j++) {
cin >> mapp[i][j];
}
}
int res = 0;
dfs(0, 0);
cout << ans << endl;
memset(vis, 0, sizeof vis);
memset(mapp, 0, sizeof mapp);
}
return 0;
}
因为题目要求按照每行选取的点,逐行DFS。
DFS传的两个参数x和sum分别代表当前该遍历的行和已选的点的数目,用来判断是否终止,还要用ans记录满足条件的数量,超过三个之后只记录ans而不输出。
我们通过按行遍历,每行按列遍历的方式,就可以保证答案满足字典序。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define inf 0x3f3f3f3f
using namespace std;
typedef long long LL;
const int MAXN = 200;
const int LEN = 200;
int n, ans;
int mapp[MAXN][MAXN];
bool row[MAXN], col[MAXN], dg[MAXN], udg[MAXN];
void dfs(int x, int sum) {
if(x == n+1) {
if(sum == n) {
if(ans < 3) {
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= n; j++) {
if(mapp[i][j] == 1) {
cout << j << " ";
}
}
}
cout << endl;
}
ans++;
}
return;
}
for(int j = 1; j <= n; j++) {
if(!row[x] && !col[j] && !dg[j-x+n] && !udg[x+j]) {
row[x] = col[j] = dg[j-x+n] = udg[x+j] = 1;
mapp[x][j] = 1;
dfs(x+1, sum+1);
row[x] = col[j] = dg[j-x+n] = udg[x+j] = 0;
mapp[x][j] = 0;
}
}
}
int main() {
cin >> n;
dfs(1, 0);
cout << ans;
return 0;
}
就是在图中,从起点找终点的问题。第一发TLE了,原因在于,找完一个点(x,y)是否需要回溯。
如果从(x,y)无法到达终点,那么从其他地方到达(x,y)也无法再到达终点,所以无需回溯。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define inf 0x3f3f3f3f
using namespace std;
typedef long long LL;
const int MAXN = 600;
int n, m;
char mapp[MAXN][MAXN];
bool vis[MAXN][MAXN];
int stx, sty, endx, endy;
int dx[] = {1, 0, -1, 0};
int dy[] = {0, 1, 0, -1};
bool flag = 0;
void dfs(int x, int y) {
if(x == endx && y == endy) {
flag = 1;
return;
}
for(int i = 0; i < 4; i++) {
int a = x + dx[i], b = y + dy[i];
if(a >= 0 && a < n && b >= 0 && b < m && !vis[a][b] && mapp[a][b] != '#') {
vis[a][b] = 1;
dfs(a, b);
// 是否需要回溯
// 如果从这个点无法到达终点,则下次从其他点到达(x,y)后也无法到达终点
// vis[a][b] = 0;
}
}
}
int main() {
cin >> n >> m;
for(int i = 0; i < n; i++) {
cin >> mapp[i];
}
for(int i = 0; i < n; i++) {
for(int j = 0; j < m; j++) {
if(mapp[i][j] == 'g') {
endx = i, endy = j;
}
if(mapp[i][j] == 's') {
stx = i, sty = j;
}
}
}
dfs(stx, sty);
if(flag) {
cout << "Yes";
} else {
cout << "No";
}
return 0;
}
将整数nn分成kk份,且每份不能为空,任意两个方案不相同(不考虑顺序)。
例如:n=7n=7,k=3k=3,下面三种分法被认为是相同的。
1,1,5;
1,5,1;
5,1,1.
问有多少种不同的分法。
这个题的n的范围是200,单纯DFS是跑不过来的,所以需要先写出DFS,再剪枝。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define inf 0x3f3f3f3f
using namespace std;
typedef long long LL;
const int MAXN = 205;
int n, k;
int ans;
void dfs(int u, int sum, int cur) {
if(cur == k) {
if(sum == n) {
ans++;
}
return;
}
for(int i = u; sum + i * (k - cur) <= n; i++) {
dfs(i, sum + i, cur+1);
}
}
int main() {
cin >> n >> k;
dfs(1, 0, 0);
cout << ans;
return 0;
}
邻接矩阵建图,分别以每个点为起点DFS,看能到达的最远距离。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define inf 0x3f3f3f3f
using namespace std;
typedef long long LL;
const int MAXN = 25;
int n, m, ans;
int mapp[MAXN][MAXN];
bool vis[MAXN];
/*
题目并没有保证所有点是连通的
那我们就无法用总点数判断结尾
所以选定一个点尽管DFS就好了
*/
// 当前点和总共的点数
void dfs(int num, int res) {
ans = max(ans, res);
for(int j = 1; j <= n; j++) {
// 连通并且这个点没有被选过
if(mapp[num][j] > 0 && !vis[j]) {
vis[j] = 1;
dfs(j, res + mapp[num][j]);
vis[j] = 0;
}
}
}
int main() {
cin >> n >> m;
while(m--) {
int x, y, z;
cin >> x >> y >> z;
mapp[x][y] = mapp[y][x] = z;
}
for(int i = 1; i <= n; i++) {
memset(vis, 0, sizeof vis);
vis[i] = 1;
dfs(i, 0);
}
cout << ans << endl;
return 0;
}
const int MAXN = 1e3+10;
int n, m;
// g用来存图 dis用来存距离
int g[MAXN][MAXN];
int dis[MAXN][MAXN];
queue<pair<int, int> > q;
int bfs() {
memset(dis, -1, sizeof dis);
int dx[4] = {1, 0, -1, 0}, dy[4] = {0, 1, 0, -1};
q.push({0, 0});
dis[0][0] = 0;
while(!q.empty()) {
auto t = q.front();
q.pop();
for(int i = 0; i < 4; i++) {
int x = t.first + dx[i], y = t.second + dy[i];
if(x >= 0 && x < n && y >= 0 && y < m && dis[x][y] == -1 && g[x][y] == 0) {
dis[x][y] = dis[t.first][t.second] + 1;
q.push({x, y});
}
}
}
return dis[n-1][m-1];
}
int main() {
cin >> n >> m;
for(int i = 0; i < n; i++) {
for(int j = 0; j < m; j++) {
cin >> g[i][j];
}
}
cout << bfs() << endl;
return 0;
}
经典BFS,左对齐宽五格输出printf("%-5d", vis[i][j]);
然后写BFS的时候细心点就行了。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define inf 0x3f3f3f3f
using namespace std;
typedef pair<int, int> PII;
typedef long long LL;
const int MAXN = 500;
int n, m, stx, sty;
int vis[MAXN][MAXN];
int dx[] = {1, 1, 2, 2, -1, -1, -2, -2},
dy[] = {2, -2, 1, -1, 2, -2, 1, -1};
void bfs() {
queue<PII> q;
q.push({stx, sty});
vis[stx][sty] = 0;
while(q.size()) {
auto t = q.front();
q.pop();
int x = t.first, y = t.second;
for(int i = 0; i < 8; i++) {
int a = x + dx[i], b = y + dy[i];
if(a >= 1 && a <= n && b >= 1 && b <= m && vis[a][b] == -1) {
vis[a][b] = vis[x][y] + 1;
q.push({a, b});
}
}
}
return;
}
int main() {
cin >> n >> m >> stx >> sty;
memset(vis, -1, sizeof vis);
bfs();
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= m; j++) {
printf("%-5d", vis[i][j]);
}
printf("\n");
}
return 0;
}
同样是个简单的BFS题,一个点可以像马一样走日或者走田,经典BFS。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define inf 0x3f3f3f3f
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
const int MAXN = 1e5+10;
int dx[] = {1, 1, 2, 2, 2, 2, -1, -1, -2, -2, -2, -2};
int dy[] = {2, -2, 1, -1, 2, -2, 2, -2, 1, -1, 2, -2};
int vis[22][22];
int bfs(int x, int y) {
memset(vis, 0, sizeof vis);
queue<PII> q;
q.push({x, y});
vis[x][y] = 0;
while(q.size()) {
auto t = q.front();
q.pop();
for(int i = 0; i < 12; i++) {
int a = t.first + dx[i], b = t.second + dy[i];
if(a >= 1 && a <= 20 && b >= 1 && b <= 20 && !vis[a][b]) {
vis[a][b] = vis[t.first][t.second] + 1;
q.push({a, b});
}
}
}
return vis[1][1];
}
int main() {
int x1, y1, x2, y2;
cin >> x1 >> y1;
cin >> x2 >> y2;
cout << bfs(x1, y1) << endl;
cout << bfs(x2, y2) << endl;
return 0;
}
给出一个NN个顶点MM条边的无向无权图,顶点编号为1-N1−N。问从顶点11开始,到其他每个点的最短路有几条。
这个题我们可以将这张图想象成一颗树,从根节点1开始遍历。
因为有环或者重边,第一次遍历到的点对其标记,并且设置深度;之后如果重复遍历到这个点,并且已经访问过了,那么这个点的最短路的数量就得加上上一个点最短路的数量 cnt[k] = (cnt[k] + cnt[t]) % mod;
。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define inf 0x3f3f3f3f
using namespace std;
typedef long long LL;
const int MAXN = 1e6+10;
const int mod = 100003;
int n, m;
bool vis[MAXN];
int cnt[MAXN], deep[MAXN];
//vector G[MAXN];
/*
第一次访问 置vis 置深度
第二次访问(已知深度) 置cnt: cnt[t] += cnt[x]
*/
int h[MAXN], ne[MAXN], e[MAXN], idx;
void add(int a, int b) {
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
void bfs() {
queue<int> q;
deep[1] = 0, cnt[1] = 1, vis[1] = 1;
q.push(1);
while(q.size()) {
int t = q.front();
// cout << t << endl;
q.pop();
for(int i = h[t]; ~i; i = ne[i]) {
int k = e[i];
if(!vis[k]) {
vis[k] = 1;
deep[k] = deep[t] + 1;
q.push(k);
}
if(deep[k] == deep[t] + 1) {
cnt[k] = (cnt[k] + cnt[t]) % mod;
}
}
}
}
/*
void bfs() {
queue q;
deep[1] = 0, cnt[1] = 1, vis[1] = 1;
q.push(1);
while(q.size()) {
int t = q.front();
q.pop();
for(int i = 0; i < G[t].size(); i++) {
int k = G[t][i];
if(!vis[k]) {
vis[k] = 1;
deep[k] = deep[t] + 1;
q.push(k);
}
if(deep[k] == deep[t] + 1) {
cnt[k] = (cnt[k] + cnt[t]) % mod;
}
}
}
}
*/
int main() {
memset(h, -1, sizeof h);
cin >> n >> m;
for(int i = 0; i < m; i++) {
int x, y;
cin >> x >> y;
add(x, y), add(y, x);
}
bfs();
for(int i = 1; i <= n; i++) {
cout << cnt[i] << endl;
}
return 0;
}
说在最后的话:编写实属不易,若喜欢或者对你有帮助记得点赞 + 关注或者收藏哦~