深度优先算法(DFS)和广度优先算法(BFS)是两种常见的图遍历算法,用于搜索或遍历图中的节点。它们的主要区别在于访问节点的顺序。
1. 深度优先算法(DFS)
核心思想:尽可能深地访问图的分支,直到无法继续为止,然后回溯并探索其他分支。
实现方式:
- 递归:通过递归调用实现。
- 栈:使用栈来模拟递归过程。
步骤:
- 从起始节点开始,标记为已访问。
- 选择一个未访问的相邻节点,递归访问。
- 如果没有未访问的相邻节点,回溯到上一个节点,继续探索。
特点:
- 适用于寻找所有可能的路径或解。
- 可能陷入无限循环(需设置访问标记)。
- 空间复杂度较低,适合深度较大的图。
应用场景:
- 拓扑排序
- 连通分量检测
- 解决迷宫问题
实际算法问题
经典的岛屿数量 问题。给定一个二维网格 grid
,其中 '1' 表示陆地,'0' 表示水域,你需要计算这个网格中有多少个岛屿。假设陆地总是连成一片,并且不会形成环。
问题描述
-
输入 :一个由字符 '0' 和 '1' 组成的二维数组
grid
。 -
输出:返回网格中岛屿的数量。
-
约束条件:
- 网格的大小为 m x n。
- 1 <= m, n <= 300
- grid[i][j] 的值要么是 '0' 要么是 '1'。
示例
PLAINTEXT
输入:
[
['1','1','0','0','0'],
['1','1','0','0','0'],
['0','0','1','0','0'],
['0','0','0','1','1']
]
输出: 3
解释: 上面的网格有3个岛屿。
解决方案
我们可以使用DFS来解决这个问题。遍历整个网格,每当遇到一个未访问过的陆地('1'),我们就启动一次DFS探索这片陆地,并标记已访问过的陆地以避免重复计数。每次新启动的DFS表示发现了一个新的岛屿。
JavaScript 实现
JAVASCRIPT
function numIslands(grid) {
if (!grid || grid.length === 0) return 0;
const rows = grid.length;
const cols = grid[0].length;
let count = 0;
function dfs(r, c) {
// 检查边界条件和是否已经访问过
if (r < 0 || r >= rows || c < 0 || c >= cols || grid[r][c] === '0') return;
// 标记当前节点为已访问
grid[r][c] = '0';
// 对上下左右四个方向进行递归
dfs(r - 1, c); // 上
dfs(r + 1, c); // 下
dfs(r, c - 1); // 左
dfs(r, c + 1); // 右
}
for (let i = 0; i < rows; i++) {
for (let j = 0; j < cols; j++) {
if (grid[i][j] === '1') { // 找到一个新岛屿
count++;
dfs(i, j);
}
}
}
return count;
}
// 测试用例
const grid = [
['1','1','0','0','0'],
['1','1','0','0','0'],
['0','0','1','0','0'],
['0','0','0','1','1']
];
console.log(numIslands(grid)); // 输出: 3
这段代码定义了numIslands
函数用于计算给定网格中的岛屿数量,并通过调用辅助函数dfs
来实现深度优先搜索。
2. 广度优先算法(BFS)
核心思想:从起始节点开始,逐层向外扩展,先访问所有相邻节点,再访问下一层节点。
实现方式:
- 队列:使用队列来存储待访问节点。
步骤:
- 从起始节点开始,标记为已访问并加入队列。
- 从队列中取出一个节点,访问其所有未访问的相邻节点,标记并加入队列。
- 重复上述步骤,直到队列为空。
特点:
- 适用于寻找最短路径(无权图)。
- 空间复杂度较高,适合广度较大的图。
- 不会陷入无限循环(需设置访问标记)。
应用场景:
- 最短路径问题(无权图)
- 社交网络中的层级关系分析
- 广播网络中的消息传播
实际算法问题
最短路径问题。给定一个迷宫(用二维数组表示),找到从起点到终点的最短路径长度。迷宫中的 0 表示可以通过的位置,1 表示障碍物不能通过。
问题描述
- 输入 :一个二维数组
maze
表示迷宫,以及起点坐标 (startX, startY) 和终点坐标 (endX, endY)。 - 输出:从起点到终点的最短路径长度。如果无法到达,则返回 -1。
示例
JAVASCRIPT
const maze = [
[0, 1, 0, 0, 0],
[0, 1, 0, 1, 0],
[0, 0, 0, 1, 0],
[0, 1, 1, 1, 0],
[0, 0, 0, 0, 0]
];
const startX = 0, startY = 0;
const endX = 4, endY = 4;
// 结果: 从(0,0)到(4,4)的最短路径长度为7
解决方案
我们将使用 BFS 算法来寻找最短路径。BFS 是适合这种类型问题的理想选择,因为它可以按层次扩展节点,确保首先找到的是最短路径。
下面是实现代码:
JAVASCRIPT
function shortestPath(maze, startX, startY, endX, endY) {
// 检查边界条件
if (maze[startX][startY] === 1 || maze[endX][endY] === 1) return -1;
const rows = maze.length;
const cols = maze[0].length;
const directions = [[1, 0], [-1, 0], [0, 1], [0, -1]]; // 下、上、右、左方向
let queue = [[startX, startY, 0]]; // 队列存储当前位置及当前步数
const visited = new Set(); // 记录访问过的点
while (queue.length > 0) {
const [x, y, steps] = queue.shift();
// 如果达到终点,直接返回当前步数
if (x === endX && y === endY) return steps;
for (let [dx, dy] of directions) {
const newX = x + dx;
const newY = y + dy;
// 检查新位置是否越界或已经访问过或不可达
if (newX >= 0 && newX < rows && newY >= 0 && newY < cols &&
maze[newX][newY] === 0 && !visited.has(`${newX},${newY}`)) {
visited.add(`${newX},${newY}`);
queue.push([newX, newY, steps + 1]);
}
}
}
// 如果遍历完整个图都没有找到路径,说明没有可达路径
return -1;
}
// 测试用例
console.log(shortestPath(maze, 0, 0, 4, 4)); // 输出 7
这段代码定义了一个名为 shortestPath
的函数,它接受迷宫矩阵及其起始与终止坐标作为参数,并返回最短路径长度。该算法通过维护一个队列来进行广度优先搜索,同时利用集合 visited
来避免重复访问同一位置。
对比
特性 | DFS | BFS |
---|---|---|
访问顺序 | 深度优先 | 广度优先 |
数据结构 | 栈 | 队列 |
空间复杂度 | O(h)(h为深度) | O(w)(w为最大宽度) |
适用场景 | 寻找所有路径、拓扑排序 | 最短路径、层级遍历 |
循环风险 | 可能陷入无限循环 | 不会陷入无限循环 |
总结
- DFS 适合深度较大的图或需要探索所有路径的场景。
- BFS 适合广度较大的图或需要寻找最短路径的场景。
根据具体问题选择合适的算法。