相同点:
深搜和宽搜都可以对空间进行遍历,搜索的结构都是树
不同点:
深搜(DFS):(暴搜)(直男)(执着的人)
(1)尽可能往深了搜,当搜到叶节点(简称搜到头)就会回溯,然后再搜下一个,然后再回溯,然后再搜下一个,然后再回溯,再搜下一个......(边回去边看能不能往下走是不是真的无路可走了)。
(2)用栈(stack)实现
(3)搜的时候只需要记录这条路径上的所有点,因此使用的空间和高度是成正比的,越深存的越多
(4)不具最短性
(5)回溯,剪枝
(6)每个DFS都对应一条搜索树
(7)算法、思路比较奇怪的、需要空间比较大的都用DFS
(8)最重要的就是顺序,要用什么样的顺序来把情况都遍历一遍
模型:
void dfs(int step)
{
//判断边界
{
...
}
//尝试每一种可能(每一种尝试就是一种扩展)
for(i = 1 ; i <= n ; i ++)
{
}
//返回
return;
}
题目:
题目1:全排列
题目描述:
给定一个整数 n,将数字 1∼n排成一排,将会有很多种排列方法。
现在,请你按照字典序将所有的排列方法输出。
输入格式
共一行,包含一个整数 n。
输出格式
按字典序输出所有排列方案,每个方案占一行。
数据范围
1≤n≤7
输入样例:
3
输出样例:
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1
AC代码:
啊哈版本:
#include<bits/stdc++.h>
using namespace std;
const int N = 1e3 + 10;
int a[N],book[N],n; //a表示小盒子
void dfs(int step) //step表示现在站在第几个盒子面前
{
int i;
if(step == n + 1) // 如果站在第n + 1个盒子面前,则表示前n个盒子
{
// 输出一种排列(1~n号盒子中的扑克牌编号)
for(i = 1 ; i <= n ;i ++) cout << " " << a[i];
cout << endl;
return;
}
//此时站在第step个盒子面前,应该放哪张牌呢?
//按照1,2,3,...,n的顺序一一尝试
for(i = 1 ; i <= n ; i ++)
{
//判断扑克牌i是否还在手上
if(book[i] == 0) // book[i]等于0表示i号扑克牌在手上
{
//开始尝试使用扑克牌;
a[step] = i; // 将i号牌放入第step个盒子中
book[i] = 1; // 将book[i]设为1,表示i号扑克牌已经不在手上
//第step个盒子已经放好,接下来需要下一个盒子
dfs(step + 1);
book[i] = 0; // 这是非常重要的一步,一定要将刚才尝试的扑克牌收回,才能进行下一次尝试
}
}
return;
}
int main()
{
ios::sync_with_stdio(false);
cin >> n;
dfs(1);
getchar();
getchar();
return 0;
}
y总版本 :
#include<bits/stdc++.h>
#define endl '\n'
#define inf 0x3f3f3f3f
using namespace std;
typedef long long ll;
const int N = 10;
int n;
int state[N]; // 0表示还没放,1~n表示放了哪些数
bool used[N]; // true表示用过,false表示还未用过
//u表示一个分支(一个方案)的第几位
void dfs(int u)
{
if(u > n) // 边界
{
for(int i = 1 ; i <= n ; i ++) printf("%d ",state[i]);
puts("");
return;
}
//依次枚举每个分支,即当前位置可以填哪些数
for(int i = 1 ; i <= n ; i ++)
{
//没有被用过
if(used[i] == 0)
{
state[u] = i;
used[i] = true;
dfs(u + 1);
//恢复现场
state[u] = 0;
used[i] = false;
}
}
}
int main()
{
scanf("%d",&n);
dfs(1); // 传入第一个分支第一位
return 0;
}

题目2(DFS + 剪枝)
题目描述:
所谓虫食算,就是原先的算式中有一部分被虫子啃掉了,需要我们根据剩下的数字来判定被啃掉的字母。
来看一个简单的例子:
43#9865#045
+ 8468#6633
--------------
44445506978
其中 # 号代表被虫子啃掉的数字。
根据算式,我们很容易判断:第一行的两个数字分别是 5和 3,第二行的数字是 5。
现在,我们对问题做两个限制:
首先,我们只考虑加法的虫食算。这里的加法是 N进制加法,算式中三个数都有 N 位,允许有前导的 0。
其次,虫子把所有的数都啃光了,我们只知道哪些数字是相同的,我们将相同的数字用相同的字母表示,不同的数字用不同的字母表示。
如果这个算式是 N进制的,我们就取英文字母表的前 N 个大写字母来表示这个算式中的 0 到 N−1 这 N 个不同的数字:但是这 N 个字母并不一定顺序地代表 0 到 N−1。
输入数据保证 N个字母分别至少出现一次。
BADC
+ CBDA
----------
DCCC
上面的算式是一个 4进制的算式。很显然,我们只要让 ABCD分别代表 0123,便可以让这个式子成立了。
你的任务是,对于给定的 N进制加法算式,求出 N个不同的字母分别代表的数字,使得该加法算式成立。输入数据保证有且仅有一组解。
输入格式
输入包含 4行。
第一行有一个正整数 N(N≤26),后面的3行每行有一个由大写字母组成的字符串,分别代表两个加数以及和。这3个字符串左右两端都没有空格,并且恰好有 N位。
输出格式
输出包含一行。在这一行中,应当包含唯一的那组解。解是这样表示的:输出 N个数字,分别表示 A,B,C……所代表的数字,相邻的两个数字用一个空格隔开,不能有多余的空格。
输入样例:
5
ABCED
BDACE
EBBAA
输出样例:
1 0 3 4 2
思路:
搜索顺序:依次枚举每个字符对应哪个数字
剪枝:
1.从低位向高位依次考虑每一位:
a,b,c,t
被加数 加数 和 进位
(a+b+t) mod n=c
2.由于和也是n位数 ,因此最高位不可以有进位
3.从最低位开始枚举每个未知数
AC代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 30;
int path[N]; // 每个字母对应的数字
int q[N]; // 从低位到高位字母出现的顺序
int book[N];
char e[3][N];
int n;
bool check()
{
int i;
int t = 0;
for (i = n - 1; i >= 0 ; i --)
{
int a = path[e[0][i] - 'A'];
int b = path[e[1][i] - 'A'];
int c = path[e[2][i] - 'A'];
if (a != -1 && b != -1 && c != -1)
{
// a b c 的数值都确定了
if (t == -1)
{
if ((a + b) % n != c && (a + b + 1) % n != c) return false; // // 进位不确定 当t=0和t=1都不成立的时候返回false
if (i == 0 && a + b >= n) return false; // 如果最高位有进位返回false
}
else
{
if ((a + b + t) % n != c) return false; // t确定的时候
if (i == 0 && a + b + t >= n) return false;
t = (a + b + t) / n;// 进位为这个
}
}
// 如果a b c 没用都确定 则 进位也不确定 t=-1
else t = -1;
}
return true;
}
// 注意 这里dfs定义为bool型 因为要判断每一位是否出错然后剪枝
// 如果有一位不成立 则可以终止这条子树的搜索
bool dfs(int u)
{
int i;
if (u == n) return true;
for (i = 0; i < n ; i ++) // 枚举数字
{
if (!book[i])
{
book[i] = true;
path[q[u]] = i; // 如果这个数字没使用过 赋值给当前最右的字母 q[u]
if (check() && dfs(u + 1)) return true; // 如果check没问题dfs下一个 有一位出问题就返回false
else
{
path[q[u]] = -1;
book[i] = false;
}
}
}
return false;
}
int main()
{
int i,j;
int tail = 0;
cin >> n;
for (i = 0 ; i < 3 ; i ++) cin >> e[i];
for (i = n - 1, tail = 0 ; i >= 0 ; i --)//从最低位开始枚举三个字符串的对应位
{
for (j = 0 ; j < 3 ; j ++)
{
int c = e[j][i] - 'A';
if (!book[c])//如果这个字母没标记 这时的book是记录字母是否出现过
{
book[c] = true;
q[tail ++] = c;//放入队列 记录字母从低到高的出现顺序
}
}
}
memset(book, 0, sizeof book); //将book数组清0 后面的book记录数字是否使用过
memset(path, -1, sizeof path);
dfs(0);
for (i = 0; i < n ; i++) cout << path[i] << " ";
return 0;
}
宽搜(BFS):(海王)(稳重的人)
(1)一层一层搜,可以同时看很多很多条路
(2)用队列(queue)实现
(3)搜的时候会把一层的节点都存下来,那么它所需要的空间就是指数级别的,很大,因此宽搜在空间上吃大亏
(4)因为宽搜是一层一层往外搜,所以它每次搜的点都是最近的,因此它和最短路有关系
题目:
题目1:
小哼解救小哈:
#include<bits/stdc++.h>
using namespace std;
struct note
{
int x; // 横坐标
int y; // 纵坐标
int f; // 父亲在队列中的编号,本题不要求输出路径,可以不需要f
int s;
};
int main()
{
struct note que[2501]; // 因为地图大小不超过50*50,因此队列扩展不会超过2500个
int a[51][51] = {0},book[51][51] = {0}; // 数组book的用作是记录哪些点已经在队列中了,防止一个点被重复扩展,并全部初始化为0
//定义一个用于表示走的方向的数组
int next[4][2] = { {0,1}, // 向右走
{1,0}, // 向下走
{0,-1}, // 向上走
{-1,0} }; //向上走
int head,tail;
int i,j,k,n,m,startx,starty,p,q,tx,ty,flag;
cin >> n >> m;
for(i = 1 ; i <= n ; i ++)
{
for(j = 1 ; j <= m ; j ++)
{
cin >> a[i][j];
}
}
cin >> startx >> starty >> p >> q;
//队列初始化
head = 1;
tail = 1;
//往队列插入迷宫入口坐标
que[tail].x = startx;
que[tail].y = starty;
que[tail].f = 0;
que[tail].s = 0;
tail ++;
book[startx][starty] = 1;
flag = 0; // 用来标记是否到达目标点,0表示暂时还没有到达,1表达到达
//当队列不为空的时候循环
while(head < tail)
{
//枚举4个方向
for(k = 0 ; k <= 3 ; k ++)
{
//计算下一个点的坐标
tx = que[head].x + next[k][0];
ty = que[head].y + next[k][1];
//判断是否越界
if(tx < 1 || tx > n || ty < 1 || ty > m) continue;
//判断是否是障碍物或者已经在路径中
if(a[tx][ty] == 0 && book[tx][ty] == 0)
{
//把这个点标记为已经走过
//注意宽搜每个点只入队一次,所以和深搜不同,不需要将book数组还原
book[tx][ty] = 1;
//插入新的点到队列中
que[tail].x = tx;
que[tail].y = ty;
que[tail].f = head; // 因为这个点是从head扩展出来的,所以它的父亲是head,本题目不需要路径,因此本句可省略
que[tail].s = que[head].s + 1; // 步数是父亲的步数 + 1
tail ++;
}
//如果到目标点了,停止扩展,任务结束,退出循环
if(tx == p && ty == q)
{
flag = 1;
break;
}
}
if(flag == 1) break;
head ++; // 注意这地方千万不要忘记,当一个点扩展结束后,head ++才能对后面的点再进行扩展
}
//打印队列中末尾最后一个点(目标点)的步数
//注意tail是指向队列队尾(即最后一位)的下一个位置,所以这需要-1;
cout << que[tail - 1].s;
getchar();
getchar();
return 0;
}
题目2:
给定一个 n×m 的二维整数数组,用来表示一个迷宫,数组中只包含 0 或 1,其中 0 表示可以走的路,1表示不可通过的墙壁。最初,有一个人位于左上角 (1,1)处,已知该人每次可以向上、下、左、右任意一个方向移动一个位置。
请问,该人从左上角移动至右下角 (n,m)处,至少需要移动多少次。
数据保证 (1,1)处和 (n,m) 处的数字为 0,且一定至少存在一条通路。
输入格式
第一行包含两个整数 n和 m。
接下来 n行,每行包含 m 个整数(0 或 1),表示完整的二维数组迷宫。
输出格式
输出一个整数,表示从左上角移动至右下角的最少移动次数。
数据范围
1≤n,m≤100
输入样例:
5 5
0 1 0 0 0
0 1 0 1 0
0 0 0 0 0
0 1 1 1 0
0 0 0 1 0
输出样例:
8
AC代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> PII;
const int N = 110;
int n,m;
int g[N][N]; // 存图
int d[N][N]; // 存步数
int bfs()
{
queue<PII> q;
memset(d, -1 ,sizeof d); // 初始化距离
d[0][0] = 0; // 表示0走过了,且步数为0
q.push({0,0}); // 将(0,0)点放入队列
int dx[4] = {-1, 0 , 1, 0}, dy[4] = {0, 1, 0, -1};
while(q.size())
{
auto t = q.front(); // 取出队头
q.pop(); // 弹出对头
for(int i = 0 ; i < 4 ; i ++) // 对当前点枚举四个方向
{
int x = t.first + dx[i], y = t.second + dy[i]; // 下一个点的坐标为(x,y)
if(x >= 0 && x < n && y >= 0 && y < m && g[x][y] == 0 && d[x][y] == -1) // 下一个点的位置合理且不是障碍且没有走过
{
d[x][y] = d[t.first][t.second] + 1; // 移动下一个点步数加1
q.push({x,y}); // 将(x,y)点放入队列
}
}
}
return d[n - 1][m - 1];
}
int main()
{
scanf("%d %d",&n,&m);
for(int i = 0 ; i < n ; i ++)
{
for(int j = 0 ; j < m ; j ++)
{
scanf("%d",&g[i][j]);
}
}
int res = bfs();
printf("%d\n",res);
}
题目3:(抓住那头牛)
题目描述:
农夫知道一头牛的位置,想要抓住它。
农夫和牛都位于数轴上,农夫起始位于点 N,牛位于点 K。
农夫有两种移动方式:
1.从 X 移动到 X−1 或 X+1,每次移动花费一分钟
2.从 X 移动到 2∗X,每次移动花费一分钟
假设牛没有意识到农夫的行动,站在原地不动。
农夫最少要花多少时间才能抓住牛?
输入格式
共一行,包含两个整数N和K。
输出格式
输出一个整数,表示抓到牛所花费的最少时间。
数据范围
0 ≤ N, K ≤ 10^5
输入样例:
5 17
输出样例:
4
AC代码:(数组模拟队列)
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<vector>
#include<set>
#include<map>
#define inf 0x3f3f3f3f
#define endl '\n'
#include<utility>
#include<queue>
using namespace std;
typedef long long ll;
typedef pair<int, int> PII;
const int N = 2e5 + 10;
int n,k;
int q[N];
int dist[N];
int bfs()
{
memset(dist, -1, sizeof dist);
dist[n] = 0; // n已经走过,0步
q[0] = n; // 把n加到队列里面去,进行以后的扩展
int hh = 0, tt = 0;
while(hh <= tt)
{
int t = q[hh]; // 取出队头
hh ++; // 弹出队头元素
if(t == k) return dist[k];
//三种扩展方式
if(t + 1 < N && dist[t + 1] == -1)
{
dist[t + 1] = dist[t] + 1;
q[++ tt] = t + 1;
}
if(t - 1 >= 0 && dist[t - 1] == -1)
{
dist[t - 1] = dist[t] + 1;
q[++ tt] = t - 1;
}
if(t * 2 < N && dist[t * 2] == -1)
{
dist[t * 2] = dist[t] + 1;
q[++ tt] = t * 2;
}
}
return -1;
}
int main()
{
scanf("%d %d",&n,&k);
int res = bfs();
printf("%d\n",res);
return 0;
}
AC代码:(STL)
#include<bits/stdc++.h>
#include<queue>
using namespace std;
int const N = 2e5 + 10;
queue<int> q;
int dist[N]; // dist[i]表示到i点一共走过的步数
int bfs(int n, int k) // 参数表示起点和终点
{
memset(dist , -1 , sizeof dist);
dist[n] = 0; // n这个点已经走过
q.push(n); // 把第一个点(n点)加入队列,对以后进行扩展
while(!q.empty())
{
int t = q.front(); // t表示当前的点
q.pop();
// 已经到达终点
if(t == k) return dist[t];
// 三种扩展方式
// 右移一步
if(t + 1 < N && dist[t + 1] == -1) // 当前点的下一个点不过限 且 没有被扩展过
{
dist[t + 1] = dist[t] + 1;
q.push(t + 1);
}
// 左移一步
if(t - 1 >= 0 && dist[t - 1] == -1) // 当前点的下一个点不过限 且 没有被扩展过
{
dist[t - 1] = dist[t] + 1;
q.push(t - 1);
}
//
if(t * 2 < N && dist[t * 2] == -1) // 当前点的下一个点不过限 且 没有被扩展过
{
dist[t * 2] = dist[t] + 1;
q.push(t * 2);
}
}
return -1;
}
int main()
{
int n,k;
scanf("%d %d",&n,&k);
printf("%d\n",bfs(n,k));
return 0;
}
题目四:最小操作次数(蓝桥模拟赛)
有一个整数 A=2021,每一次,可以将这个数加 1 、减 1 或除以 2,其中除以 2 必须在数是偶数的时候才允许。
例如,2021 经过一次操作可以变成 2020、2022。
再如,2022 经过一次操作可以变成 2021、2023 或 1011。
请问,2021 最少经过多少次操作可以变成 1。
类似最短路径和最少操作次数这样的题都可以用bfs来求解
答案:14
分析:
为什么想到用BFS呢?
答:因为bfs就是从一个点出发,在当前位置上可以有上下左右四种走法;而这道题是从2021出发,可以有+1,-1,/2三种走法,所以是同类型的题,我们可以用bfs来求解

AC代码:
import java.io.*;
import java.math.BigInteger;
import java.util.*;
import java.util.stream.Collectors;
public class Main
{
static PrintWriter pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.out)));
static int N = (int)2e5 + 10;
static Queue<Integer> q = new LinkedList<>();
static int cnt[] = new int[N]; // cnt[i]表示n变到i最少需要多少步
static int R;
static int C;
static int bfs(int n)
{
Arrays.fill(cnt,-1);
cnt[n] = 0;
q.add(n);
while(!q.isEmpty())
{
int t = q.poll();
if(t == 1) return cnt[t];
if(t % 2 != 0 && cnt[t + 1] == -1)
{
cnt[t + 1] = cnt[t] + 1;
q.add(t + 1);
}
if(t % 2 != 0 && cnt[t - 1] == -1)
{
cnt[t - 1] = cnt[t] + 1;
q.add(t - 1);
}
if(t % 2 == 0 && cnt[t / 2] == -1)
{
cnt[t / 2] = cnt[t] + 1;
q.add(t / 2);
}
}
return -1;
}
public static void main(String[] args ) throws IOException
{
int n = rd.nextInt();
pw.println(bfs(n));
pw.flush();
}
}
class rd
{
static BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
static StringTokenizer tokenizer = new StringTokenizer("");
static String nextLine() throws IOException { return reader.readLine(); }
static String next() throws IOException
{
while(!tokenizer.hasMoreTokens()) tokenizer = new StringTokenizer(reader.readLine());
return tokenizer.nextToken();
}
static int nextInt() throws IOException { return Integer.parseInt(next()); }
static double nextDouble() throws IOException { return Double.parseDouble(next()); }
static long nextLong() throws IOException { return Long.parseLong(next()); }
static BigInteger nextBigInteger() throws IOException
{
BigInteger d = new BigInteger(rd.nextLine());
return d;
}
}
class math
{
int gcd(int a,int b)
{
if(b == 0) return a;
else return gcd(b,a % b);
}
int lcm(int a,int b)
{
return a * b / gcd(a, b);
}
// 求n的所有约数
List get_factor(int n)
{
List<Long> a = new ArrayList<>();
for(long i = 1; i <= Math.sqrt(n) ; i ++)
{
if(n % i == 0)
{
a.add(i);
if(i != n / i) a.add(n / i); // // 避免一下的情况:x = 16时,i = 4 ,x / i = 4的情况,这样会加入两种情况 ^-^复杂度能减少多少是多少
}
}
// 相同因子去重,这个方法,完美
a = a.stream().distinct().collect(Collectors.toList());
// 对因子排序(升序)
Collections.sort(a);
return a;
}
// 判断是否是质数
boolean check_isPrime(int n)
{
if(n < 2) return false;
for(int i = 2 ; i <= n / i; i ++) if (n % i == 0) return false;
return true;
}
}
class PII implements Comparable<PII>
{
int x,y;
public PII(int x ,int y)
{
this.x = x;
this.y = y;
}
public int compareTo(PII a)
{
if(this.x-a.x != 0)
return this.x-a.x; //按x升序排序
else return this.y-a.y; //如果x相同,按y升序排序
}
}
class Edge
{
int a,b,c;
public Edge(int a ,int b, int c)
{
this.a = a;
this.b = b;
this.c = c;
}
}
class Line implements Comparable<Line>
{
double k; // 斜率
double b; // 截距
public Line(double k, double b)
{
this.k = k;
this.b = b;
}
@Override
public int compareTo(Line o)
{
if (this.k > o.k) return 1;
if (this.k == o.k)
{
if (this.b > o.b) return 1;
return -1;
}
return -1;
}
}
本文详细介绍了深度优先搜索(DFS)与宽度优先搜索(BFS)的基本概念、特点及应用,通过对比两者的不同之处,辅以典型例题解析,帮助读者深入理解这两种搜索算法。
1万+

被折叠的 条评论
为什么被折叠?



