关于深度优先搜索与宽/广度优先搜索
在解决一些较复杂的问题时候,只会一些很简单的算法如:贪心,简单枚举,模拟,分治...是远远不够的,还需要了解一些除此之外的算法,这篇文章将带你了解搜索基础:dfs(下面简称深搜)与bfs(下面简称广搜)。
什么是深度优先搜索与宽/广度优先搜索
深搜和广搜都是以一定的顺序遍历整张图的算法,算法上的搜索主要包括两类:深搜与广搜,狭义的深搜还包括 A*(A Star) 和 IDA* ,深搜的搜索思想是从一个结点开始不断下探,直到无路可走在进行回溯,而广搜则是将此层遍历完后再搜索先一层,例如下面这棵树:
使用深搜的遍历顺序是 1-2-5-3-6-7-4
而使用广搜则是 1-2-3-4-5-6-7
深度优先搜索
深度优先搜索本质是枚举的一种,和普通枚举的区别是普通枚举有明确的层次,而搜索则没有这一点,因此需要用递归实现,深搜的模板如下:
void dfs(需要的数据) { if(符合条件)//剪枝 { return; } if(找到了想要的数据) { 处理数据 return; } for(遍历所有情况) { if(符合条件) { 标记已搜索 dfs(...) //继续搜索 回溯 } }
例题: 骑士的移动
题目描述:
输入8*8的国际象棋棋盘上的2个格子(列:a-h,行:1-8),求马至少多少步从起点(键盘输入的第一个位置)跳到终点(键盘输入的第二个位置)。
解析:
从题目中可以看出是棋盘问题,棋盘类的题目一般用搜索或者动态规划来解决,这一题则应使用搜索来解决,但最短路问题最好使用bfs,但用dfs更能体现简直所在。
首先需要解决输入的为字符串的问题,字母可以使用: 字母 -'a'+1 的方式转换为数字,数字则用: 数字-'0' 来解决。
接着套上模板写出代码:
错误代码示范(超时)
#include<bits/stdc++.h> using namespace std; string a,b; int ans=1000000,ax,ay,bx,by; int ne[8][2]={{-2,1},{-1,2},{1,2},{2,1},{2,-1},{1,-2},{-1,-2},{-2,1}}; int vis[25][25]={0}; void dfs(int x,int y,int n) { if(x==bx&&y==by) { ans=min(ans,n); return; } for(int i =0;i<8;i++) { int nx=x+ne[i][0]; int ny=y+ne[i][1]; if(nx>=1&&nx<=8&&ny>=1&&ny<=8&&vis[nx][ny]==0) { vis[nx][ny]=1; dfs(nx,ny,n+1); vis[nx][ny]=0; } } } int main() { while(cin>>a>>b) { memset(vis,0,sizeof(vis)); ans=10000000; ax=a[0]-'a'+1; ay=a[1]-'0'; bx=b[0]-'a'+1; by=b[1]-'0'; dfs(ax,ay,0); cout<<"To get from "<<a<<" to "<<b<<" takes "<<ans<<" knight moves."<<endl; } return 0; }
超时原因是因为这一题用dfs时间复杂度太高,优化不到位,可以这样解决:如果当前找到的答案大于已找到的答案则直接返回:
正确代码示例:
#include<bits/stdc++.h> using namespace std; string a,b; int ans=1000000,ax,ay,bx,by; int ne[8][2]={{-2,1},{-1,2},{1,2},{2,1},{2,-1},{1,-2},{-1,-2},{-2,-1}}; int vis[25][25]={0}; void dfs(int x,int y,int n) { if(x>=1&&y<=8&&y>=1&&y<=8&&n<ans) { if(x==bx&&y==by) { ans=n; } for(int i =0;i<8;i++) { int nx=x+ne[i][0]; int ny=y+ne[i][1]; if(nx>=1&&nx<=8&&ny>=1&&ny<=8&&vis[nx][ny]==0) { vis[nx][ny]=1; dfs(nx,ny,n+1); vis[nx][ny]=0; } } } } int main() { while(cin>>a>>b) { memset(vis,0,sizeof(vis)); ans=10000000; ay=a[0]-'a'+1; ax=a[1]-'0'; by=b[0]-'a'+1; bx=b[1]-'0'; dfs(ax,ay,0); cout<<"To get from "<<a<<" to "<<b<<" takes "<<ans<<" knight moves."<<endl; } return 0; }
完美AC!
广度优先搜索
一般涉及到最单路问题是,使用bfs的效率远高于dfs,因为广度优先搜索是一层一层的遍历,所以不需要回溯。
bfs靠队列实现,其原理是,遍历这一层的所有点,当符合条件时,入队将数据存储起来.
什么是队列
队列是 STL 中一种先进先出的数据结构 ,使用时应带上头文件<queue>.
队列主要有以下操作:
1.建立队列: queue<数据类型> 队列名,如 queue<int> q;
2.获取队首元素 : 队列名.front(),如 q.front();
3.弹出对手元素:队列名.pop(),如 q.pop();
4.判断队列是否为空:队列名.empty(),如 q.empty();
下面是bfs的模板:
void dfs(int x) { queue<int> q; q.push(x); int u; 标记起始位置已访问 while(!q.empty()) { u=q,front(); q.pop(); 处理数据 for(遍历所有情况) { if(符合条件) { 入队 标记已访问 } } } }
例题:填涂颜色
题目描述:
由数字 0 组成的方阵中,有一任意形状闭合圈,闭合圈由数字 1 构成,围圈时只走上下左右 4 个方向。现要求把闭合圈内的所有空间都填写成 2
解析:
很明显的一道bfs 题目(也可以用dfs),只需要找到封闭的换里面的一个 0 然后进行 广搜 就可以了,但要怎么去找呢? 通过观察样例可以看出,按照从上到下,从左到右的顺序遍历矩阵,第一个 1 右下角的 0 就是环里面的数,这样一来代码就很容易实现了:
#include<bits/stdc++.h> using namespace std; int n,m[35][35],vis[35][35]={0},a[35][35]; int ne[4][2]={{1,0},{0,1},{-1,0},{0,-1}}; void bfs(int x,int y) { queue<int> q; q.push(x); q.push(y); vis[x][y]=1; m[x][y]=2; int u,v; while(!q.empty()) { u=q.front(); q.pop(); v=q.front(); q.pop(); m[u][v]=2; for(int i = 0;i<=3;i++) { int a=u+ne[i][0]; int b=v+ne[i][1]; if(a>=1&&a<=n&&b>=1&&b<=n&&vis[a][b]==0&&m[a][b]==0) { q.push(a); q.push(b); vis[a][b]=1; } } } } int main() { scanf("%d",&n); for(int i = 1;i<=n;i++) for(int j = 1;j<=n;j++) { cin>>m[i][j]; } for(int i = 1;i<=n;i++) { for(int j = 1;j<=n;j++) { if(m[i][j]==1) { bfs(i+1,j+1); break; } } break; } for(int i = 1;i<=n;i++) { for(int j = 1;j<=n;j++) { cout<<m[i][j]<<" "; } cout<<endl; } return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)