深度优先算法(DFS)和广度优先算法(BFS)

深度优先算法(DFS)和广度优先算法(BFS)是两种常见的图遍历算法,用于搜索或遍历图中的节点。它们的主要区别在于访问节点的顺序。

1. 深度优先算法(DFS)

核心思想:尽可能深地访问图的分支,直到无法继续为止,然后回溯并探索其他分支。

实现方式

  • 递归:通过递归调用实现。
  • :使用栈来模拟递归过程。

步骤

  1. 从起始节点开始,标记为已访问。
  2. 选择一个未访问的相邻节点,递归访问。
  3. 如果没有未访问的相邻节点,回溯到上一个节点,继续探索。

特点

  • 适用于寻找所有可能的路径或解。
  • 可能陷入无限循环(需设置访问标记)。
  • 空间复杂度较低,适合深度较大的图。

应用场景

  • 拓扑排序
  • 连通分量检测
  • 解决迷宫问题

实际算法问题

经典的岛屿数量 问题。给定一个二维网格 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)

核心思想:从起始节点开始,逐层向外扩展,先访问所有相邻节点,再访问下一层节点。

实现方式

  • 队列:使用队列来存储待访问节点。

步骤

  1. 从起始节点开始,标记为已访问并加入队列。
  2. 从队列中取出一个节点,访问其所有未访问的相邻节点,标记并加入队列。
  3. 重复上述步骤,直到队列为空。

特点

  • 适用于寻找最短路径(无权图)。
  • 空间复杂度较高,适合广度较大的图。
  • 不会陷入无限循环(需设置访问标记)。

应用场景

  • 最短路径问题(无权图)
  • 社交网络中的层级关系分析
  • 广播网络中的消息传播

实际算法问题

最短路径问题。给定一个迷宫(用二维数组表示),找到从起点到终点的最短路径长度。迷宫中的 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 适合广度较大的图或需要寻找最短路径的场景。

根据具体问题选择合适的算法。

相关推荐
Yoyo25年秋招冲冲冲2 分钟前
Day63_20250211_图论part7 prim算法|kruskal算法精讲
java·数据结构·算法·深度优先·图论
患得患失94941 分钟前
【前端】【面试】ref与reactive的区别
前端·面试·vue3
bitenum1 小时前
【C/C++】位段
c语言·数据结构·c++·算法·青少年编程
AndrewHZ1 小时前
DeepSeek-R1技术革命:用强化学习重塑大语言模型的推理能力
人工智能·python·深度学习·算法·语言模型
brzhang1 小时前
麻了,Expo 出了一个 a0.dev,可以一句话生成一个 react native App,这下移动端客户端!卒!
前端·后端
大模型铲屎官1 小时前
CSS 性能优化全攻略:提升网站加载速度与流畅度
前端·css·性能优化·html·css3·css性能优化
Lightning-py2 小时前
工具-screen-管理终端会话(服务器长时间运行任务)
linux·运维·服务器·前端·chrome
迷途小码农零零发2 小时前
React进行路由跳转的方法汇总
前端·javascript·react.js
林的快手2 小时前
HTML 简介
java·服务器·前端·算法·spring·html
某柚啊2 小时前
vscode设置保存时自动缩进和格式化
前端·javascript·vscode·编辑器