本文将通过分析一个典型的搜索问题,阐述广度优先搜索算法(BFS)和深度优先搜索算法(DFS)的基本实现方法。
导言
BFS(广度优先搜索)和DFS(深度优先搜索)是图或树遍历中的两种核心算法,主要区别体现在以下方面:
BFS
- 逐层访问节点,先访问根节点的所有相邻节点,再按层次扩展至下一层节点,确保按“近→远”顺序遍历。例如二叉树的BFS遍历顺序为根节点→左子节点→右子节点→孙节点。
- 使用队列(FIFO),按先进先出原则管理待访问节点。
- 空间复杂度:需要存储当前层的所有节点,空间复杂度为 O(V)(V为节点数)
- 时间复杂度:两者时间复杂度均为 O(V+E)(V为节点数,E为边数)
- 应用场景:
- 最短路径问题:如迷宫最短路径、社交网络中的最小关系链
- 层级分析:如按层次遍历二叉树、网络路由
DFS
- 沿一条路径尽可能深入,直到无法继续时回溯到上一节点,再探索其他分支。例如二叉树的DFS遍历可能先访问根节点→左子树的最深节点,再回溯访问右子树。
- 使用栈(LIFO)或递归实现,后进先出的机制支持回溯操作。
- 空间复杂度:递归深度取决于树的高度,空间复杂度为 O(h)(h为树高)
- 时间复杂度:两者时间复杂度均为 O(V+E)(V为节点数,E为边数)
- 应用场景:
- 深层结构探索:如检测环路、拓扑排序、连通性分析。
- 回溯问题:如排列组合生成、迷宫路径全解枚举。
下面将针对一道例题,同时使用 BFS 和 DFS算法,以探讨两者区别。
通过例题讲解BFS和DFS的区别
P1605 迷宫
题目描述
给定一个 N×MN \times MN×M 方格的迷宫,迷宫里有 TTT 处障碍,障碍处不可通过。
在迷宫中移动有上下左右四种方式,每次只能移动一个方格。数据保证起点上没有障碍。
给定起点坐标和终点坐标,每个方格最多经过一次,问有多少种从起点坐标到终点坐标的方案。
输入格式
第一行为三个正整数 N,M,TN,M,TN,M,T,分别表示迷宫的长宽和障碍总数。
第二行为四个正整数 SX,SY,FX,FYSX,SY,FX,FYSX,SY,FX,FY,SX,SYSX,SYSX,SY 代表起点坐标,FX,FYFX,FYFX,FY 代表终点坐标。
接下来 TTT 行,每行两个正整数,表示障碍点的坐标。
输出格式
输出从起点坐标到终点坐标的方案总数。
输入输出样例 #1
输入 #1
2 2 1
1 1 2 2
1 2
输出 #1
1
说明/提示
对于 100%100\%100% 的数据,1≤N,M≤51 \le N,M \le 51≤N,M≤5,1≤T≤101 \le T \le 101≤T≤10,1≤SX,FX≤n1 \le SX,FX \le n1≤SX,FX≤n,1≤SY,FY≤m1 \le SY,FY \le m1≤SY,FY≤m。
广度优先搜索(BFS)算法
伪代码
function BFS(graph, start_node):
create a queue Q // 创建一个队列 Q 来存储待访问的节点[^1]
mark start_node as visited and enqueue it into Q // 将起点标记为已访问,并加入队列[^2]
while Q is not empty: // 当队列不为空时循环执行
node = dequeue(Q) // 取出队首节点
visit(node) // 访问该节点
for each neighbor of node in graph: // 遍历当前节点的所有邻居节点
if neighbor is not visited: // 如果邻居节点未被访问过
mark neighbor as visited // 标记邻居节点为已访问
enqueue(neighbor to Q) // 将邻居节点加入队列
return "BFS completed"
完整代码:使用队列
#include <iostream>
#include <queue>
using namespace std;
// State用于标记当前程序所在的点
struct State {
int x, y;
// 建立二维数组用于标记是否访问过
vector<vector<bool>> visited;
};
int main(){
// 输入第一行为三个正整数 N,M,T,分别表示迷宫的长宽和障碍总数。
int n, m, t;
cin >> n >> m >> t;
//第二行为四个正整数 SX,SY,FX,FY,SX,SY 代表起点坐标,FX,FY 代表终点坐标。
int sx, sy, fx, fy;
cin >> sx >> sy >> fx >> fy;
// 建立maze二维数组用于存储地图,其中true代表障碍
vector<vector<bool>> maze(n+1, vector<bool>(m+1, false));
// 用于初始化visited数组
vector<vector<bool>> init_visited(n+1, vector<bool>(m+1, false));
while(t--){
// 读取障碍坐标并保存到 maze
int x, y;
cin >> x >> y;
maze[x][y] = true;
}
// 建立方向数组,用于定义四个移动方向
int dir[4][2] = {{1,0}, {-1,0}, {0,1}, {0,-1}};
// 初始化队列,将起点入队
queue<State> q;
init_visited[sx][sy] = true; // 标记起点已访问
q.push({sx, sy, init_visited});
//用于存储路径的总数
int count = 0;
while(!q.empty()){ // 当队列不为空时,继续循环
State current = q.front(); // 取出队首元素
q.pop(); // 移除队首元素
// 到达终点,路径数+1
if(current.x == fx && current.y == fy){
count++;
continue; // 不继续扩展终点节点
}
// 遍历四个移动方向
for(int i=0; i<4; i++){
int nx = current.x + dir[i][0]; // 计算新坐标
int ny = current.y + dir[i][1]; // 计算新坐标
// 检查新坐标是否越界
if(nx < 1 || nx > n || ny < 1 || ny > m) continue; // 越界跳过
// 检查新坐标是否为障碍或已访问过
if(maze[nx][ny] || current.visited[nx][ny]) continue; // 是则跳过
// 深度拷贝访问状态:创建新访问矩阵
vector<vector<bool>> new_visited = current.visited;
new_visited[nx][ny] = true; // 标记新坐标已访问
// 将新坐标入队
q.push({nx, ny, new_visited});
}
}
cout << count << endl;
return 0;
}
深度优先搜索算法(DFS)
伪代码 (使用递归)
function DFS(node):
if node is null: // 如果节点为空,则直接返回
return
visit(node) // 访问当前节点 (例如打印节点值或其他操作)
mark node as visited // 将当前节点标记为已访问
for each neighbor of node: // 遍历当前节点的所有邻居节点
if neighbor is not visited: // 如果邻居节点未被访问
DFS(neighbor) // 对该邻居节点递归执行DFS函数
伪代码 (使用栈)
function IterativeDFS(start_node):
stack = new Stack() // 创建一个新的栈
stack.push(start_node) // 将起始节点压入栈中
while stack is not empty: // 当栈不为空时循环
node = stack.pop() // 取出栈顶元素
if node has not been visited: // 如果取出的节点尚未访问
visit(node) // 访问该节点
mark node as visited // 标记该节点为已访问
for each neighbor of node in reverse order: // 遍历当前节点的所有邻居节点(逆序)
stack.push(neighbor)
完整代码: 使用栈
#include <iostream>
#include <queue>
#include <stack> // 添加栈头文件
using namespace std;
// State用于标记当前程序所在的点
struct State {
int x, y;
// 建立二维数组用于标记是否访问过
vector<vector<bool>> visited;
};
int main(){
// 输入第一行为三个正整数 N,M,T,分别表示迷宫的长宽和障碍总数。
int n, m, t;
cin >> n >> m >> t;
//第二行为四个正整数 SX,SY,FX,FY,SX,SY 代表起点坐标,FX,FY 代表终点坐标。
int sx, sy, fx, fy;
cin >> sx >> sy >> fx >> fy;
// 建立maze二维数组用于存储地图,其中true代表障碍
vector<vector<bool>> maze(n+1, vector<bool>(m+1, false));
// 用于初始化visited数组
vector<vector<bool>> init_visited(n+1, vector<bool>(m+1, false));
while(t--){
// 读取障碍坐标并保存到 maze
int x, y;
cin >> x >> y;
maze[x][y] = true;
}
// 建立方向数组,用于定义四个移动方向
int dir[4][2] = {{1,0}, {-1,0}, {0,1}, {0,-1}};
// 初始化队列,将起点入队
// 将队列改为栈
stack<State> s;
init_visited[sx][sy] = true;
s.push({sx, sy, init_visited});
int count = 0;
while(!s.empty()) {
State current = s.top(); // 取栈顶元素
s.pop(); // 移除栈顶元素
if(current.x == fx && current.y == fy) {
count++;
continue; // 不继续扩展终点节点
}
// 遍历四个移动方向
for(int i=0; i<4; i++){
int nx = current.x + dir[i][0]; // 计算新坐标
int ny = current.y + dir[i][1]; // 计算新坐标
// 检查新坐标是否越界
if(nx < 1 || nx > n || ny < 1 || ny > m) continue; // 越界跳过
// 检查新坐标是否为障碍或已访问过
if(maze[nx][ny] || current.visited[nx][ny]) continue; // 是则跳过
// 深度拷贝访问状态:创建新访问矩阵
vector<vector<bool>> new_visited = current.visited;
new_visited[nx][ny] = true; // 标记新坐标已访问
// 将新坐标入队
s.push({nx, ny, new_visited});
}
}
cout << count << endl;
return 0;
}