🍨🍨🍨这一章,我们来重点看一些常见的搜索题型,包括暴力枚举、广度优先搜索(BFS)、递归及其应用、深度优先搜索(DFS)、搜索剪枝技巧、终极骗分技巧等内容。希望能帮助大家更好地掌握计算机考研机试中所涉及到的搜索问题。
目录
🧊🧊🧊6.1 暴力枚举
定义
枚举算法是在分析问题时,逐个列举出所有可能情况,然后根据条件判断此答案是否合适,合适就保留,不合适就丢弃,最后得出一个结论。主要利用计算机运算速度快、精确度高的特点,对要解决问题的所有可能情况,一个不漏地进行检验,从中找出符合要求的答案,因此枚举法是通过牺牲时间来换区答案的全面性。
举例说明
有一个整数 ABCD,一定是四位数,A 不能为 0,其中 ABCD*4=DCBA,A、B、C、D 都是一个数字,求 ABCD 是多少?
解决这个问题,我们可以直接枚举 ABCD 四个数的值:
#include <bits/stdc++.h>
using namespace std;
int main() {
for (int A = 0; A <= 9; A++) {
for (int B = 0; B <= 9; B++) {
for (int C = 0; C <= 9; C++) {
for (int D = 0; D <= 9; D++) {
if (A == 0) continue;
int s1 = A * 1000 + B * 100 + C * 10 + D;
int s2 = D * 1000 + C * 100 + B * 10 + A;
if (s1 * 4 == s2) printf("%d\n", s1);
}
}
}
}
return 0;
}
经过计算,答案为:2178
这种方法就是枚举,优点是直接了当,缺点是复杂度高。
(我们可以做一个最简单优化,看最高位可知,D>=A*4,因此 A 只能取值 1 和 2)
🥥练习题目:
DreamJudge 1348 百鸡问题
#include<bits/stdc++.h>
using namespace std;
int main()
{
int n;
while(cin>>n)
{
for(int i=0;i<=100;i++)
{
for(int j=0;j<=100;j++)
{
for(int k=0;k<=100;k++)
{
if(i+j+k==100&&5*i+3*j+(1.0/3)*k<=n) cout<<"x="<<i<<",y="<<j<<",z="<<k<<endl;
}
}
}
}
return 0;
}
DreamJudge 1165 abc
#include<bits/stdc++.h>
using namespace std;
int main()
{
for(int a=0;a<=9;a++)
for(int b=0;b<=9;b++)
for(int c=0;c<=9;c++)
{
int abc=a*100+b*10+c,bcc=b*100+c*10+c;
if(abc+bcc==532&&static_cast<int>(log10(abc))==2&&static_cast<int>(log10(bcc))==2)
cout<<a<<" "<<b<<" "<<c<<endl;
}
return 0;
}
DreamJudge 1274 Old Bill
#include <bits/stdc++.h>
using namespace std;
int main()
{
int n;
int x,y,z;
int flag=0;
while(cin>>n)
{
cin>>x>>y>>z;
flag=0;
for(int i=9;i>0;i--)
{
for(int j=9;j>=0;j--)
{
int num=i*10000+x*1000+y*100+z*10+j;
if(num%n==0)
{
flag=1;
cout<<i<<" "<<j<<" "<<num/n<<endl;
break;
}
}
if(flag==1) break;
}
if(flag==0) cout<<0<<endl;
}
return 0;
}
🧊🧊🧊6.2 广度优先搜索(BFS)
广度优先搜索其实简单的理解就是从起点开始一层一层的扩散出去,要实现这个一层一层的扩散就要用到队列的先进先出的思想,所以一般我们都用队列来实现广度优先搜索算法。
🥥例题:DreamJudge 1563
#include <bits/stdc++.h>
using namespace std;
const int maxn = 100 + 5;
char mpt[maxn][maxn];
int vis[maxn][maxn];
int dir[4][2] = {0, 1, 1, 0, 0, -1, -1, 0};
struct node {
int x, y;
int step;
};
//使用广度优先搜索求解
int bfs(int sx, int sy) {
memset(vis, 0, sizeof(vis));
queue<node> q;//使用队列来维护一层层发散的优先级
q.push(node{sx, sy, 0});
vis[sx][sy] = 1;
int ans = -1;
while(!q.empty()) {
node now = q.front();
q.pop();
if (mpt[now.x][now.y] == 'E') {//找到终点
ans = now.step;
break;
}
for (int i = 0; i < 4; i++) {//上下左右四个方向
int nx = now.x + dir[i][0];
int ny = now.y + dir[i][1];
if ((mpt[nx][ny] == '*' || mpt[nx][ny] == 'E')&&vis[nx][ny] == 0) {
q.push(node{nx, ny, now.step + 1});
vis[nx][ny] = 1;
}
}
}
return ans;
}
int main() {
int h, w;
while (scanf("%d%d", &h, &w) != EOF) {
if (h == 0 && w == 0) break;
int sx = 0, sy = 0;
memset(mpt, 0, sizeof(mpt));
for (int i = 1; i <= h; i++) {
scanf("%s", mpt[i] + 1);
for (int j = 1; j <= w; j++) {
if (mpt[i][j] == 'S') {
sx = i, sy = j;//记录起点坐标
}
}
}
int ans = bfs(sx, sy);
printf("%d\n", ans);
}
return 0;
}
🥥练习题目:
DreamJudge 1308 迷宫逃离 2 🍰
这道题目首先需要利用广度优先搜索计算出每个困于迷宫中的人移动到迷宫中每个位置需要去掉障碍物的最少数目,最后通过遍历迷宫中的每个位置,得到每个人移动到迷宫中每个位置需要去掉障碍物的数目的和的最小值即可。
//摘自N诺用户:Dear_Mr_He
#include<bits/stdc++.h>
using namespace std;
const int inf = 0x3f3f3f3f; // 定义无穷大
const int maxn = 100 + 5;
struct node {
int x, y;
};
int n, m;
char mpt[maxn][maxn]; // 存储迷宫结构
int away[maxn][maxn][3]; // 最后一个维度代表困于迷宫中的人数,整个矩阵用于存储每个人到迷宫中每个位置需要去掉的障碍物的数目
int dir[4][2] = {{0, 1}, {0, -1}, {-1, 0}, {1, 0}}; // 移动的四个方向:向上、向下、向左、向右
node peoples[3]; // 表示困于迷宫中的人
// 使用广度优先搜索求解
void bfs(int index) { // index 表示困于迷宫中的人的编号
queue<node> q; // 使用队列来维护一层层发散的优先级
node now;
now.x = peoples[index].x;
now.y = peoples[index].y;
q.push(now);
away[now.x][now.y][index] = 0;
while (!q.empty()) {
now = q.front();
q.pop();
for (int i = 0; i < 4; ++i) { // 上下左右四个方向
int nx = now.x + dir[i][0];
int ny = now.y + dir[i][1];
// 保证移动后的位置依然位于迷宫内
if (nx >= 1 & nx <= n & ny >= 1 & ny <= m) {
int tmp = away[now.x][now.y][index];
// 当前移动至的位置有障碍物,那么到达这个点需要去掉的障碍物数目加 1
if (mpt[nx][ny] == '#') ++tmp;
// 如果从其他位置移动至当前位置需要去掉的障碍物更少,那么更新 away 矩阵中此位置的值
if (tmp < away[nx][ny][index]) {
away[nx][ny][index] = tmp;
q.push(node{nx, ny});
}
}
}
}
}
int main() {
while (scanf("%d%d", &n, &m) != EOF) {
memset(away, inf, sizeof(away));
// 输入迷宫结构,每次输入一个字符串,从第一行第一列开始
for (int i = 1; i <= n; ++i) scanf("%s", mpt[i] + 1);
// 记录困于迷宫中的人的初始位置
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
if (mpt[i][j] == 'w') peoples[0].x = i, peoples[0].y = j;
else if (mpt[i][j] == 'W') peoples[1].x = i, peoples[1].y = j;
else if (mpt[i][j] == 'f') peoples[2].x = i, peoples[2].y = j;
}
}
// 对困于迷宫中的每个人计算出到每个位置需要去掉的最少障碍物数目
bfs(0), bfs(1), bfs(2);
int ans = inf;
// 如果困于迷宫中每个人两两互相可达,那么他们可以集聚在一个点,下面枚举每一个这样的点
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
int tmp = 0;
for (int k = 0; k < 3; ++k) tmp += away[i][j][k];
// 如果聚集点在迷宫中是存在障碍物的,那么消耗障碍物数会多算 2 次,因为 3 个人到这个点消了 3 次,实际上只需要消 1 次即可
if (mpt[i][j] == '#') tmp -= 2;
ans = tmp < ans ? tmp : ans;
}
}
printf("%d\n", ans);
}
}
DreamJudge 1124 生化武器 2 🍰
//摘自N诺用户:Dear_Mr_He
#include<bits/stdc++.h>
using namespace std;
const int maxn = 100 + 2;
char mpt[maxn][maxn]; // 存储房间结构
int dir[4][2] = {{0, 1}, {0, -1}, {-1, 0}, {1, 0}}; // 毒气的扩散方向:上、下、左、右
int T, N, M, t, gx, gy, sx, sy; // 测试案例数目、房间的高、宽、毒气扩散时间、表演的地点、观看表演的地点
struct node {
int x, y;
};
// 使用广度优先搜索求解,返回值为真说明在 t 秒时间内 SJ 还是安全的,输出现在房间里的情况,否则输出 No
bool bfs(int gx, int gy, int sx, int sy, int t) {
queue<node> gq, sq; // 使用队列来维护一层层发散的优先级
gq.push(node{gx, gy});
sq.push(node{sx, sy});
while (t--) {
int gq_size = gq.size(); // 当前队列里元素的个数,遍历当前队列的元素,将当前秒内房间的毒气进行扩散
// 毒气扩散
while (gq_size--) {
node gnow = gq.front();
gq.pop();
for (int i = 0; i < 4; ++i) { // 上下左右四个方向
int gnx = gnow.x + dir[i][0];
int gny = gnow.y + dir[i][1];
// 保证扩散到的位置是位于房间内并且是还没被毒气充满的位置
if (gnx >= 1 & gnx <= N & gny >= 1 & gny <= M & mpt[gnx][gny] != 'X' & mpt[gnx][gny] != 'G') {
mpt[gnx][gny] = 'G';
gq.push(node{gnx, gny});
}
}
}
// 毒气无法扩散了则退出循环
if (gq.empty()) break;
// SJ在当前秒内进行移动
int sq_size = sq.size();
while (sq_size--) {
node snow = sq.front();
sq.pop();
if (mpt[snow.x][snow.y] == 'S') {
for (int i = 0; i < 4; ++i) { // 上下左右四个方向
int snx = snow.x + dir[i][0];
int sny = snow.y + dir[i][1];
if (snx >= 1 & snx <= N & sny >= 1 & sny <= M & mpt[snx][sny] == '.') {
mpt[snx][sny] = 'S';
sq.push(node{snx, sny});
}
}
}
}
}
for (int i = 1; i <= N; ++i)
for (int j = 1; j <= M; ++j)
if (mpt[i][j] == 'S') return true;
return false;
}
int main() {
scanf("%d", &T);
while (T--) {
scanf("%d%d%d", &N, &M, &t);
memset(mpt, 0, sizeof(mpt));
// 输入房间结构,每次输入一个字符串,从第一行第一列开始
for(int i = 1; i <= N; i++) cin >> mpt[i] + 1;
// 找到表演和观看表演的地点
for (int i = 1; i <= N; ++i) {
for (int j = 1; j <= M; ++j) {
if (mpt[i][j] == 'G') gx = i, gy = j;
else if (mpt[i][j] == 'S') sx = i, sy = j;
}
}
if (bfs(gx, gy, sx, sy, t)) {
for (int i = 1; i <= N; ++i) {
for (int j = 1; j <= M; ++j) printf("%c", mpt[i][j]);
printf("\n");
}
} else printf("No\n");
}
}
DreamJudge 1126 生化武器 🍰
//摘自N诺用户:Dear_Mr_He
#include<bits/stdc++.h>
using namespace std;
const int maxn = 30 + 2;
char mpt[maxn][maxn]; // 存储房间结构
int dir[4][2] = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}}; // 毒气的扩散方向:上、下、左、右
int n, m, t, x, y; // 房间的高、宽、毒气扩散时间、表演的地点
struct node {
int x, y;
};
// 使用广度优先搜索求解,返回值为真说明在 t 秒时间内毒气没有充满房间或刚好充满,输出现在房间里的情况,否则输出 NO
bool bfs(int x, int y, int t) {
mpt[x][y] = '#'; // 首先设置表演地点位置有毒气
queue<node> q; // 使用队列来维护一层层发散的优先级
q.push(node{x, y});
while (t--) {
int q_size = q.size(); // 当前队列里元素的个数,遍历当前队列的元素,将当前秒内房间的毒气进行扩散
while (q_size--) {
node now = q.front();
q.pop();
for (int i = 0; i < 4; ++i) { // 上下左右四个方向
int nx = now.x + dir[i][0];
int ny = now.y + dir[i][1];
// 保证扩散到的位置是位于房间内并且是还没被毒气充满的位置
if (nx >= 1 & nx <= n & ny >= 1 & ny <= m & mpt[nx][ny] == '.') {
mpt[nx][ny] = '#';
q.push(node{nx, ny});
}
}
}
if (q.empty()) break; // 如果队列为空,说明毒气已经在上一秒就充满了可以扩散到的所有位置
}
// 因为墙的作用,有些位置毒气无法扩散至,故最后毒气是无法充满整个房间的
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
if (mpt[i][j] == '.') return true;
}
}
// 其实在 t 秒时间内毒气刚好充满整个房间,个人感觉这句话有歧义,一种理解是在 t 秒内毒气充满了就行,有时间多也可以,另一种理解是在 t 秒时毒气就刚好充满,如果有时间多是不行的
// 像样例中的第二组数据,如果 t 为 3 ,第一种理解是输出 NO,第二种理解是输出房间里的情况
// 但是两种理解的判断条件都 AC 了,应该是测试数据不足够的导致的,所以目前我也不知道哪种理解是真正符合题目意思的
if (t <= 0 & !q.empty()) return true; // 这是第一种理解
// if (t <= 0) return true; // 这是第二种理解
else return false;
}
int main() {
while (scanf("%d%d%d", &n, &m, &t) != EOF) {
memset(mpt, 0, sizeof(mpt));
// 输入房间结构,每次输入一个字符串,从第一行第一列开始
for (int i = 1; i <= n; ++i) scanf("%s", mpt[i] + 1);
// 找到表演的地点
for (int i = 0; i <= n; ++i) {
for (int j = 0; j <= m; ++j) {
if (mpt[i][j] == '*') x = i, y = j;
}
}
if (bfs(x, y, t)) {
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) printf("%c", mpt[i][j]);
printf("\n");
} printf("\n");
} else printf("NO\n");
}
}
🧊🧊🧊6.3 递归及其应用
定义
递归算法(英语:recursion algorithm)在计算机科学中是指一种通过重复将问题分解为同类的子问题而解决问题的方法。计算理论可以证明递归的作用可以完全取代循环,因此在很多函数编程语言(如 Scheme)中习惯用递归来实现循环。
例:求N!
#include <stdio.h>
int fac(int x) {
if (x == 0) return 1;
return x * fac(x - 1);
}
int main() {
int n;
scanf("%d", &n);
printf("%d\n", fac(n));
return 0;
}
🥥例题:DreamJudge 1098
汉诺塔问题是一个很经典的问题了,找到移动过程中的共同点,然后通过递归的方式进行求解。
#include<iostream>
using namespace std;
int step;//移动步数
void Hanoi(int n, char a, char b, char c) {
if(n == 1) {
if((step+1) % 5 == 0)
cout << a << "-->" << c << endl;
else cout << a << "-->" << c << " ";
step++;
return;
}
Hanoi(n-1, a, c, b);
Hanoi(1, a, b, c);
Hanoi(n-1, b, a, c);
}
int main() {
int n;
while(cin>>n)
{
if(n == 0) break;
step = 0;
Hanoi(n, 'A', 'B', 'C'); cout << endl;
}
return 0;
}
🥥练习题目:
DreamJudge 1185 全排列 🍰
这道题目记住一个好用的全排列函数:next_permutation(begin,end+1);
//摘自N诺用户:wut to hust
#include<bits/stdc++.h>
using namespace std;
int main()
{
string s;
cin>>s;
char a[10];
int n=s.size();
for(int i=0;i<n;i++) a[i]=s[i];
do{
cout<<a<<endl;
}while(next_permutation(a,a+n));
return 0;
}
创作不易,点个赞吧~点赞收藏不迷路,感兴趣的宝子们欢迎关注该专栏~
勤奋努力的宝子们,学习辛苦了!宝子们可以收藏起来慢慢学哦~🌷🌷🌷休息下,我们下部分再见👋( •̀ ω •́ )✧~