2. 深度优先搜索算法(DFS)
2.1 定义/用途
从初始状态出发,沿着一条路径尽可能向深处探索,走到无法继续前进时,回溯到上一层,尝试其他分支,持续遍历状态空间,寻找目标解的搜索算法。
光看定义,DFS和枚举有几分相似。实际上,严格来讲DFS和暴力枚举的效率是相同的(大O表示法),但是搜索通常会带来更少的不必要情况列举,从而加快速度。
2.2 算法描述
DFS的内容常被戏称为“一条路走到黑”,这种算法不那么公式化,一般需要具体情况具体分析。
比如,使用树结构表示所有可能的情况
那么DFS搜索的顺序就是A→B→F→G→H→C→I→D→J→K→EA\rightarrow B \rightarrow F \rightarrow G \rightarrow H \rightarrow C \rightarrow I \rightarrow D \rightarrow J \rightarrow K \rightarrow EA→B→F→G→H→C→I→D→J→K→E
这和后面将会学到的DFS遍历树有些相像,简单说就是
DFS通常使用递归实现。下面是伪代码模板,还是树形图举例:
void dfs(int step)
{
// 递归终点:满足条件输出答案
if(step == 目标)
return;
for(枚举每一种选择){
// 做出选择
vis[x] = true;
// 向下一层搜索
dfs(step + 1);
// 回溯!撤销本次选择
vis[x] = false;
}
}
一般情况下,DFS函数总有个参数表示递归深度,用来及时终止DFS。
2.3 经典模板题
2.3.1 例题1:图遍历(最简单DFS)
题目:给定无向图,从1号点出发,遍历所有连通节点
邻接表存储图
代码位置:2\template\dfs_chart.cpp
#include <iostream>
#include <vector>
using namespace std;
vector<int> edge[105];
bool vis[105];
void dfs(int u)
{
cout << u << " ";
vis[u] = true;
for(int v : edge[u])
{
if(!vis[v])
dfs(v);
}
}
int main()
{
int n,m;
cin >> n >> m;
for(int i=1;i<=m;i++)
{
int a,b;
cin >> a >> b;
edge[a].push_back(b);
edge[b].push_back(a);
}
dfs(1);
return 0;
}
2.3.2迷宫DFS(网格地图)
'.'空地,'#'墙;起点(1,1),终点(n,m),寻找是否存在通路
网格DFS四个方向:上下左右
代码位置:2\template\dfs_maze.cpp
#include <iostream>
using namespace std;
char mp[105][105];
bool vis[105][105];
int n, m;
int dx[] = {-1, 1, 0, 0};
int dy[] = {0, 0, -1, 1};
// x,y 当前坐标
bool dfs(int x, int y)
{
// 越界,碰到墙,访问过 → 走不通
if (x < 1 || x > n || y < 1 || y > m || mp[x][y] == '#' || vis[x][y])
return false;
// 到达终点
if (x == n && y == m)
return true;
vis[x][y] = true;
// 四个方向尝试
for (int i = 0; i < 4; i++)
{
int nx = x + dx[i];
int ny = y + dy[i];
if (dfs(nx, ny))
return true;
}
// 四个方向全部走不通
return false;
}
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i++)
cin >> mp[i] + 1;
if (dfs(1, 1))
cout << "可以到达终点";
else
cout << "无路可走";
return 0;
}
2.4 小结
DFS核心注意点
-
去重:vis访问标记数组
没有vis数组 → 出现环路,无限递归→栈溢出RE。 -
递归出口一定要写!
缺少出口,无限递归直接运行错误。 -
回溯什么时候用?
- 只遍历图/连通块:不需要撤销标记;
- 枚举所有可行方案(路径、选数字、排列):搜索完一定要撤销标记
vis=false。
2359



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



