上一期我们讲了递归与递推,之前都是最最基础的武器,但这些武器不足以应对那些稍微厉害一点的怪兽,所以我们要掌握中级的武器,这一次的武器是:搜索算法。
DFS(深度优先搜索):勇往直前,不回头
想象你带着一根很长很长的绳子,每进入一个新的房间,就放一段绳子标记你的路径。你选择一条路一直走到底,直到遇到死胡同,才原路折返,捡起绳子,回到上一个岔路口,再尝试另一条路。就这样,你深入探索每一个可能的路径,直到找到宝藏。这个过程就像“走迷宫时一条路走到底,撞了南墙才回头”。
BFS(广度优先搜索):地毯式搜索,逐步推进
这次你不再用绳子,而是带了一队人马,大家排成一排一起前进。你们始终保持着队伍的宽度,遇到分叉路口,就一部分人继续沿当前路前进,其他人探索新路。这样,你们像波浪一样,一层一层地推进,直到触碰到宝藏所在的房间。这种方法保证了你总是先检查离起点近的房间,再检查远的房间,就像“湖面上扔一块石头,水波一圈一圈往外扩散”。
DFS
假设我们有一个简单的迷宫地图,用图形表示如下,其中S表示起点,E表示终点,.- 表示可以通过的路径,X表示墙壁。
S..X.
XX.X.
.X..X
..X.E
举个栗子
如果你使用DFS探索这个迷宫,你的行动可能是这样的:
- 从起点S出发,首先尝试向下走,因为那是第一个可选项。
- 到达下方的点后,发现是墙壁X,不能通过,于是回退到起点。
- 接下来尝试向右走,发现可以,于是继续向右移动到下一个点。
- 在新位置,向下是墙壁,向左是已探索过的点,因此向右走。
- 如此往复,找到了终点。
DFS的探索路径像是一条线,深入探索一条路径,遇阻则返回,再尝试其他路径。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define endl '\n'
int n;
int endx,endy;
int a[1005][1005];//迷宫地图
int v[1005][1005];//判断是否走过
int dx[]={0,1,0,-1};//用来控制上下左右的
int dy[]={1,0,-1,0};
void dfs(int x,int y)
{
if(x==endx && y==endy)
{
cout<<"到终点啦"<<endl;
return;
}
if(v[x][y]==1) return;//如果走过了 就不走
v[x][y]=1;//标记走过
for(int i=0;i<4;i++)
{
//下一步的位置
int xx=x+dx[i];
int yy=y+dy[i];
if(xx>=1&&xx<=n && yy>=1&&yy<=n && a[xx][yy]==0 && v[xx][yy]==0)//能往下走的条件 在范围内 是路 且没走过
{
dfs(xx,yy);//递归调用
v[xx][yy]=0;//回溯的过程,因为如果这一步走下去发现最后到不了终点,就要走回来
}
}
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
cin>>a[i][j];//输入地图
}
}
dfs(1,1);//假设(1,1)是起点
return 0;
}
练习题:
P1605 迷宫 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
P1451 求细胞数量 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
P1162 填涂颜色 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
P2404 自然数的拆分问题 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
P2036 [COCI2008-2009 #2] PERKET - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
BFS
举个栗子
想象一下,你正在组织一场校园寻宝活动,校园的地图可以抽象成一个网格,其中某些格子是道路(可以行走),而其他格子是建筑物(不能通过)。寻宝者从校门口出发,目标是尽快找到位于校园某个角落的宝藏。
场景设定
校园地图简化为一个二维网格,用字符表示如下:
S . . . X . . .
. . X . . . .
. X . . . X .
. . . X . . E
其中,S
表示起点(校门口),.
表示可以通行的道路,X
表示障碍物(如建筑),E
表示宝藏所在的位置。
BFS 解决方案
使用广度优先搜索策略,寻宝者将遵循以下步骤找到宝藏:
-
初始化:从起点
S
开始,将其放入一个叫做“队列”(Queue)的数据结构中,用来保存待探索的位置。同时,标记起点为已访问,以避免重复探索。 -
探索:从队列中取出第一个位置(即起点),检查它周围的每一个相邻格子(上、下、左、右,但不包括障碍物
X
)。 -
扩展:如果相邻格子尚未访问过,就将它标记为已访问,并将其加入队列。这样,队列中始终保持着一层一层的待探索位置,从起点开始向外扩展。
-
终止条件:一旦队列中的某个位置是宝藏所在的位置
E
,搜索结束。这时,我们就找到了从起点到宝藏的最短路径。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define endl '\n'
int n;
char a[1005][1005];
int v[1005][1005];
int dx[]={0,1,0,-1};
int dy[]={1,0,-1,0};
void bfs(int x,int y)
{
queue<pair<int,int> > q;//定义队列
q.push({x,y});//起点先放入队列
v[x][y]=1;//标记为走过
while(!q.empty())//循环遍历
{
int ux=q.top().first;
int uy=q.top().second;
for(int i=0;i<4;i++)//四周都走
{
int xx=ux+dx[i];
int yy=uy+dy[i];
if(xx>=1&&xx<=n && yy>=1&&yy<=n && a[xx][yy]=='.' && v[xx][yy]==0)//满足条件
{
if(xx==endx && yy==endy)
{
cout<<"找到终点啦"<<endl;
break;
}
q.push({xx,yy});//放入队列
v[xx][yy]=1; //标记为走过
}
}
}
}
int main()
{
return 0;
}
练习题:
P1443 马的遍历 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
P1135 奇怪的电梯 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
P2895 [USACO08FEB] Meteor Shower S - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
P1747 好奇怪的游戏 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
总结
- DFS是“一条道走到黑”,先深入探索一条路径,找不到再回头,适合找单一路径或确定是否存在某种解。
- BFS是“地毯式搜索”,一层一层往外扩展,适合找最短路径,因为它总是先探索距离起点更近的节点。
在编程中,DFS通常用递归来实现,就像你不断深入房间再返回;而BFS通常用队列来辅助,保证按顺序探索各个层次的节点。
百看不如一练,只有实践才是进步最快的方式,更要独立思考,如果想不出来了就看题解,会有眼前一亮的感觉。好啦,今天就到这里吧。下一期再见,记得给专栏点个关注,明天接着来哦~