前言
深度优先搜索(Depth-First Search, DFS)是一种用于遍历或搜索树或图的算法。与广度优先搜索不同,DFS 会尽可能深地搜索树的分支,直到不能再深入为止,然后回溯到前一个节点,继续搜索其他分支。
DFS算法基本原理
DFS 的基本思想非常简单:
- 从起始节点开始,标记该节点为已访问
- 对该节点的每个未访问的相邻节点,递归地应用DFS
- 如果没有未访问的相邻节点,则回溯
DFS 可以用递归或栈来实现。递归实现更为简洁,而栈实现则能更好地控制内存使用。
递归实现
1 2 3 4 5 6 7 8 9 10 11 12
| def dfs(graph, node, visited=None): if visited is None: visited = set() visited.add(node) print(node) for neighbor in graph[node]: if neighbor not in visited: dfs(graph, neighbor, visited)
|
栈实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| def dfs_iterative(graph, start): visited = set() stack = [start] while stack: node = stack.pop() if node not in visited: visited.add(node) print(node) for neighbor in graph[node]: if neighbor not in visited: stack.append(neighbor)
|
DFS的应用场景
DFS 在许多问题中都有应用,包括:
- 寻找图中的连通分量
- 拓扑排序
- 检测环
- 路径查找
- 解决迷宫、数独等问题
- 排列组合问题
DFS在LeetCode中的应用
在 LeetCode 上,有很多问题可以用 DFS 解决。下面介绍几个经典例题:
例题1:岛屿数量(LeetCode 200)
问题描述:给定一个由 ‘1’(陆地)和 ‘0’(水)组成的二维网格,计算岛屿的数量。岛屿由相邻的陆地连接而成,相邻指上下左右四个方向。
解法:使用 DFS 遍历网格,每当遇到一个陆地,就用 DFS 标记与其相连的所有陆地为已访问,计数加1。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| def numIslands(grid): if not grid: return 0 rows, cols = len(grid), len(grid[0]) count = 0 def dfs(i, j): if i < 0 or i >= rows or j < 0 or j >= cols or grid[i][j] != '1': return grid[i][j] = '#' dfs(i+1, j) dfs(i-1, j) dfs(i, j+1) dfs(i, j-1) for i in range(rows): for j in range(cols): if grid[i][j] == '1': count += 1 dfs(i, j) return count
|
例题2:路径总和(LeetCode 112)
问题描述:给定一个二叉树和一个目标和,判断该树中是否存在根节点到叶子节点的路径,使得路径上所有节点值相加等于目标和。
解法:使用 DFS 遍历二叉树的所有路径,判断是否存在满足条件的路径。
1 2 3 4 5 6 7 8 9 10 11
| def hasPathSum(root, targetSum): if not root: return False if not root.left and not root.right: return root.val == targetSum return (hasPathSum(root.left, targetSum - root.val) or hasPathSum(root.right, targetSum - root.val))
|
例题3:全排列(LeetCode 46)
问题描述:给定一个不含重复数字的数组,返回其所有可能的全排列。
解法:使用 DFS 来生成所有可能的排列。
1 2 3 4 5 6 7 8 9 10 11 12 13
| def permute(nums): result = [] def dfs(nums, path): if not nums: result.append(path) return for i in range(len(nums)): dfs(nums[:i] + nums[i+1:], path + [nums[i]]) dfs(nums, []) return result
|
例题4:单词搜索(LeetCode 79)
问题描述:给定一个二维网格和一个单词,找出该单词是否存在于网格中。单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中”相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。
解法:使用 DFS 探索网格中的所有可能路径。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| def exist(board, word): if not board or not board[0]: return False rows, cols = len(board), len(board[0]) def dfs(i, j, k): if i < 0 or i >= rows or j < 0 or j >= cols or board[i][j] != word[k]: return False if k == len(word) - 1: return True temp, board[i][j] = board[i][j], '#' found = (dfs(i+1, j, k+1) or dfs(i-1, j, k+1) or dfs(i, j+1, k+1) or dfs(i, j-1, k+1)) board[i][j] = temp return found for i in range(rows): for j in range(cols): if dfs(i, j, 0): return True return False
|
DFS的优缺点
优点
- 实现简单,代码简洁
- 空间复杂度较低(但在最坏情况下可能是 $O(n)$ )
- 适合找到所有可能的解决方案
- 在某些场景下比 BFS 更高效
缺点
- 容易陷入深度较大的分支而导致效率问题
- 可能无法找到最短路径(这是 BFS 的强项)
- 在处理环形结构时需要额外的处理
后记
深度优先搜索是一种强大而灵活的算法,在解决许多复杂问题时非常有效。通过在 LeetCode 上实践 DFS 算法,可以更好地理解其工作原理和应用场景。掌握 DFS 对于提高算法解题能力和理解图论问题至关重要。