深度优先算法(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 适合广度较大的图或需要寻找最短路径的场景。

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

相关推荐
JinSo2 分钟前
EasyEditor AI 聊天助手:让低代码开发更简单
前端·javascript·github
答案answer6 分钟前
three.js 实现几个炫酷的粒子特效(火焰,烟雾,烟花)
前端·three.js
ObjectX前端实验室21 分钟前
三年写了很多代码,也想写写自己
前端·程序员
大熊猫侯佩24 分钟前
Swift 数学计算:用 Accelerate 框架让性能“加速吃鸡”
算法·swift
满分观察网友z26 分钟前
uniapp的navigator跳转功能
前端
江城开朗的豌豆31 分钟前
Vue组件DIY指南:手把手教你玩转自定义组件
前端·javascript·vue.js
无奈何杨34 分钟前
CoolGuard风控节假日完善,QLExpress自定义函数
前端·后端
杰克尼1 小时前
2. 两数相加
算法
无聊的小坏坏1 小时前
单调栈通关指南:从力扣 84 到力扣 42
c++·算法·leetcode