【算法】搜索专题狂练,内附题单

搜索题目在常见的算法竞赛和笔试面试中很常见,尤其是蓝桥杯中,很多题都能用暴搜去找答案。

在生产上也广泛用于拓扑排序,寻路(走迷宫),搜索引擎,爬虫等,也频繁出现在高频面试题中。

DFS 与 BFS 对比

实现方法 基本思想 解决问题 N规模
DFS 栈/递归 回溯法,一次访问一条路,更接近人的思维方式, 所有解问题,或连通性问题 不能太大,<=200
BFS 队列 分治限界法,一次访问多条路,每一层需要存储大量信息 最优解问题,如最短路径 可以比较大,因为可以用队列解决,<=1000

DFS:可以使用 stack,不过多数情况下使用递归。

​ 牢记:每一个 DFS 都会对应一个搜索树,如:n 皇后问题。

BFS:一般使用 queue,也可以用数组模拟队列。

DFS 例题:

ACWing 842. 排列数字

AC代码
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;
}

ACWing 843. n-皇后问题:

AC代码
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找连通块

洛谷P1331

求连通块:经典DFS

首先,需要判断有没有不合法的船,即非矩形的。(如果一个块的右边、下边、右下边 四个块中有三个块是船,那么就是不合法的,一定无法构成矩形;否则可以构成矩形)

都合法之后,我们只需要求连通块的数量就行了:

  1. 每次选定连通块中任意一点进行DFS,对每次已经DFS过的块进行标记
  2. 注意,访问过的点要在DFS开始的时候进行标记
AC代码
#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;
}

洛谷P1596

AC代码

这是一个裸的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;
}

洛谷P1451

裸连通块问题,现在发现写这个都是有点浪费时间了。回顾一下做此题时:

  1. 注意人家是按行输入的,应该用cin读入行,用二维字符数组存起来
  2. 存图后进行DFS就可以了,还是连通块问题
AC代码
#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全排列

洛谷P1706

AC代码

经典的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 平面图问题

洛谷P1123

AC代码

好家伙,最坑爹的题目、没有之一。本来以为就按照要求DFS呗,每次更新一下当前的最大值,最后在vis数组的置0置1问题上搞了半天。

仔细想,本题是要选取若干个不互相干扰的点,求它们和的最大值。因为是搜索,并且没有最短路的性质,所以我们用DFS。而且题目的数据范围一眼望去就很小,~嗯,是个深搜的好题~。

  1. 从左上开始搜索到右下,每个点都有选和不选两种情况。选得话还得看是否满足周围没有限制条件
  2. 每个点是否可选呢?选:它周围的八个点没有被选过。
  3. 当我们选定一个点时,要对它周围的八个点标记为1,当两个点a、b、同时对c点标记为1后,第二个点要撤销标记,那么就会让c成为0,而此时a没有撤销标记,所以c不应该被置为0。
#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;
}

洛谷P1219 八皇后问题(字典序)

AC代码

因为题目要求按照每行选取的点,逐行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;
}

洛谷AT1350 起点终点可行性问题

AC代码

就是在图中,从起点找终点的问题。第一发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; 
}


DFS剪枝

洛谷P1025

将整数nn分成kk份,且每份不能为空,任意两个方案不相同(不考虑顺序)。

例如:n=7n=7,k=3k=3,下面三种分法被认为是相同的。

1,1,5;
1,5,1;
5,1,1.

问有多少种不同的分法。

这个题的n的范围是200,单纯DFS是跑不过来的,所以需要先写出DFS,再剪枝。

AC代码
#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;
}

洛谷P1294 高手去散步

邻接矩阵建图,分别以每个点为起点DFS,看能到达的最远距离。

  1. 每次DFS的时候都将当前最大距离记录;
  2. 注意:终止条件不是将n个都标记为false,而是没有能到达的点,因为题目没有保证所有点是连通的。
AC代码
#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;
}

BFS例题:

ACWing 844. 走迷宫

AC代码
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;
}

洛谷P1443 马的遍历

经典BFS,左对齐宽五格输出printf("%-5d", vis[i][j]);

然后写BFS的时候细心点就行了。

AC代码
#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;
}


洛谷P1747 好奇怪的游戏

同样是个简单的BFS题,一个点可以像马一样走日或者走田,经典BFS。

AC代码
#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;
}

洛谷P1144 最短路计数

给出一个NN个顶点MM条边的无向无权图,顶点编号为1-N1−N。问从顶点11开始,到其他每个点的最短路有几条。

这个题我们可以将这张图想象成一颗树,从根节点1开始遍历。

因为有环或者重边,第一次遍历到的点对其标记,并且设置深度;之后如果重复遍历到这个点,并且已经访问过了,那么这个点的最短路的数量就得加上上一个点最短路的数量 cnt[k] = (cnt[k] + cnt[t]) % mod;

AC代码
#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;
}

说在最后的话:编写实属不易,若喜欢或者对你有帮助记得点赞 + 关注或者收藏哦~

你可能感兴趣的:(PAT\蓝桥杯刷题,Algorithm,Training)