返回

DFS还是BFS,深度优于广度?三道LeetCode题让你悟透二者优劣!

前端

深度优先搜索(DFS)和广度优先搜索(BFS):探索多维数据结构的利器

在算法面试和刷题中,深度优先搜索(DFS)和广度优先搜索(BFS)是两个绕不开的概念。它们都是遍历图和树等数据结构的常用技术,但由于遍历方式不同,在不同的场景下具有各自的优缺点。本文将深入浅出地探讨 DFS 和 BFS 的原理、优劣势以及适用场景,帮助你掌握这些算法并在算法问题中游刃有余。

一、DFS:沿着深度探索数据结构

深度优先搜索(DFS)是一种沿着深度遍历数据结构的算法。它从根节点开始,逐层深入探索节点的子树,直到遇到叶子节点。然后,它回溯到上一个未完全探索的节点,继续探索其子树。DFS 的递归性质使其特别适合解决查找最长路径、环路检测和拓扑排序等问题。

二、BFS:沿着宽度探索数据结构

广度优先搜索(BFS)是一种沿着宽度遍历数据结构的算法。它从根节点开始,逐层探索节点的所有子节点,然后再继续探索下一层节点。这种方式可以保证所有节点都被访问到,然后再深入探索其中任何一个节点。BFS 适用于查找最短路径、图的连通性以及检测环路等问题。

三、DFS 和 BFS 的优劣势

DFS 和 BFS 虽然都是遍历数据结构的有效算法,但它们在时间复杂度、空间复杂度和适用场景上存在一些差异:

时间复杂度:

  • DFS 的时间复杂度通常比 BFS 高,因为它需要多次访问同一个节点。
  • BFS 的时间复杂度通常比 DFS 低,因为它只访问每个节点一次。

空间复杂度:

  • BFS 的空间复杂度通常比 DFS 高,因为它需要存储所有未访问的节点。
  • DFS 的空间复杂度通常比 BFS 低,因为它只存储当前路径上的节点。

适用场景:

  • DFS 适用于需要深度探索数据结构的场景,如查找最长路径或环路。
  • BFS 适用于需要广度探索数据结构的场景,如求最短路径或图的连通性。

四、LeetCode 题解示例

为了加深对 DFS 和 BFS 的理解,我们来看几个经典的 LeetCode 题目:

题目 1:路径总和

给定一个二叉树和一个目标和,求出所有从根节点到叶子节点的路径,使得这些路径上的节点值之和等于目标和。

DFS 解法:

def pathSum(root, targetSum):
    if not root:
        return []
    paths = []
    stack = [(root, [root.val], root.val)]
    while stack:
        node, path, sum = stack.pop()
        if not node.left and not node.right and sum == targetSum:
            paths.append(path)
        if node.left:
            stack.append((node.left, path + [node.left.val], sum + node.left.val))
        if node.right:
            stack.append((node.right, path + [node.right.val], sum + node.right.val))
    return paths

BFS 解法:

def pathSum(root, targetSum):
    if not root:
        return []
    queue = [(root, [root.val], root.val)]
    paths = []
    while queue:
        node, path, sum = queue.pop(0)
        if not node.left and not node.right and sum == targetSum:
            paths.append(path)
        if node.left:
            queue.append((node.left, path + [node.left.val], sum + node.left.val))
        if node.right:
            queue.append((node.right, path + [node.right.val], sum + node.right.val))
    return paths

题目 2:岛屿数量

给定一个由 0 和 1 组成的二维网格,其中 0 表示水域,1 表示陆地。求出网格中岛屿的个数。

DFS 解法:

def numIslands(grid):
    if not grid:
        return 0
    m, n = len(grid), len(grid[0])
    count = 0
    visited = set()

    def dfs(x, y):
        if x < 0 or x >= m or y < 0 or y >= n or (x, y) in visited or grid[x][y] == '0':
            return
        visited.add((x, y))
        dfs(x + 1, y)  # 上
        dfs(x - 1, y)  # 下
        dfs(x, y + 1)  # 右
        dfs(x, y - 1)  # 左

    for i in range(m):
        for j in range(n):
            if (i, j) not in visited and grid[i][j] == '1':
                count += 1
                dfs(i, j)

    return count

BFS 解法:

def numIslands(grid):
    if not grid:
        return 0
    m, n = len(grid), len(grid[0])
    count = 0
    visited = set()

    def bfs(x, y):
        queue = [(x, y)]
        while queue:
            x, y = queue.pop(0)
            if x < 0 or x >= m or y < 0 or y >= n or (x, y) in visited or grid[x][y] == '0':
                continue
            visited.add((x, y))
            queue.extend([(x + 1, y), (x - 1, y), (x, y + 1), (x, y - 1)])

    for i in range(m):
        for j in range(n):
            if (i, j) not in visited and grid[i][j] == '1':
                count += 1
                bfs(i, j)

    return count

五、常见问题解答

1. 什么是 DFS 和 BFS 的时间复杂度和空间复杂度?

  • DFS 的时间复杂度通常较高,为 O(V + E),其中 V 是顶点数,E 是边数。空间复杂度也较高,为 O(V)。
  • BFS 的时间复杂度通常较低,为 O(V + E),空间复杂度也较低,为 O(V)。

2. 什么情况下使用 DFS 比 BFS 更好?

  • 当需要深度探索数据结构时,如查找最长路径或环路时,DFS 更合适。
  • 当数据结构中有许多重叠路径时,DFS 可以避免重复遍历。

3. 什么情况下使用 BFS 比 DFS 更好?

  • 当需要广度探索数据结构时,如求最短路径或图的连通性时,BFS 更合适。
  • 当数据结构中有许多分支时,BFS 可以保证所有节点都被访问。

4. DFS 和 BFS 的主要区别是什么?

  • DFS 沿着深度遍历,BFS 沿着宽度遍历。
  • DFS 的空间复杂度通常比 BFS 高。
  • DFS 更适合深度探索,BFS 更适合广度探索。

5. 如何在算法面试中有效使用 DFS 和 BFS?

  • 理解 DFS 和 BFS 的原理、优劣势和适用场景。
  • 熟练掌握 DFS 和 BFS 的代码实现。
  • 在算法问题中能够识别适合使用 DFS 或 BFS 的场景。
  • 能够分析 DFS 和 BFS 的时间复杂度和空间复杂度。