前言
深度优先搜索(DFS)和广度优先搜索(BFS)是图或树遍历中的两种经典算法,它们有不同的特点和应用场景。
深度优先搜索(DFS)
主要特点:
-
遍历顺序:
- DFS会沿着一条路径一直走到底,直到无法继续(即走到叶子节点或无未访问的邻居节点),然后回溯到最近的分叉点,尝试其他路径。
-
使用的数据结构:
- DFS通常使用栈来存储路径(可以使用递归隐式地利用系统栈)。
-
实现方式:
- DFS通常可以通过递归或显式栈来实现。
-
时间复杂度:
- 对于图,时间复杂度为 O(V + E),其中 V 是顶点数,E 是边数。对于树,时间复杂度为 O(n),n 是节点数量。
-
空间复杂度:
- 对于递归实现,空间复杂度取决于递归的深度。在最坏的情况下,递归的深度可以达到图的深度或树的高度。空间复杂度为 O(H),其中 H 是树的高度或图的最大深度。
-
适用场景:
- DFS适合用于寻找路径或需要深入探索所有分支的场景,如解决迷宫问题、图的连通性、拓扑排序等。
广度优先搜索(BFS)
主要特点:
-
遍历顺序:
- BFS按层次进行遍历,先访问离起始节点最近的节点,然后逐层向外扩展,直到所有节点都被访问。
-
使用的数据结构:
- BFS使用队列来存储待访问的节点,保证按层次顺序访问。
-
实现方式:
- BFS通常通过队列实现。
-
时间复杂度:
- 对于图,时间复杂度为 O(V + E),其中 V 是顶点数,E 是边数。对于树,时间复杂度为 O(n)。
-
空间复杂度:
- 需要存储每一层的节点,所以空间复杂度为 O(W),其中 W 是图或树的最大宽度(即一层中最多节点的数量)。
-
适用场景:
- BFS适合用于寻找最短路径或在分层结构中查找特定元素,如图的最短路径、迷宫最短路径问题等。
二、力扣994题腐烂橘子DFS和BFS代码
1.DFS
class Solution {
int rl, cl, res;
public int orangesRotting(int[][] grid) {
int rl = grid.length, cl = grid[0].length, res = -1;
for (int r = 0; r < rl; r ++) {
for (int c = 0; c < cl; c ++) {
if (grid[r][c] == 2) {
this.dfs(grid, r, c, 3);
}
}
}
for (int r = 0; r < rl; r ++) {
for (int c = 0; c < cl; c ++) {
if (grid[r][c] == 1) {
return -1;
}
res = Math.max(res, grid[r][c]);
}
}
return res == 0 ? 0 : res - 2;
}
private void dfs(int[][] grid, int r, int c, int expect) {
int rl = grid.length, cl = grid[0].length;
if (r > 0 && (grid[r - 1][c] > expect || grid[r - 1][c] == 1)) {
grid[r - 1][c] = expect;
this.dfs(grid, r - 1, c, expect + 1);
}
if (r < rl - 1 && (grid[r + 1][c] > expect || grid[r + 1][c] == 1)) {
grid[r + 1][c] = expect;
this.dfs(grid, r + 1, c, expect + 1);
}
if (c > 0 && (grid[r][c - 1] > expect || grid[r][c - 1] == 1)) {
grid[r][c - 1] = expect;
this.dfs(grid, r, c - 1, expect + 1);
}
if (c < cl - 1 && (grid[r][c + 1] > expect || grid[r][c + 1] == 1)) {
grid[r][c + 1] = expect;
this.dfs(grid, r, c + 1, expect + 1);
}
}
}
2.BFS
class Solution {
public int orangesRotting(int[][] grid) {
if (grid == null || grid.length == 0)
return -1;
int row = grid.length;
int col = grid[0].length;
Queue<int[]> q = new LinkedList<>();
int freashOrange = 0;
for (int i = 0 ; i < row; i++) {
for (int j = 0; j < col; j++) {
if (grid[i][j] == 2) {
q.offer(new int[]{i, j});
}
if (grid[i][j] == 1)
freashOrange++;
}
}
if (freashOrange == 0)
return 0;
int time = 0;
int[][] directions = {{1, 0}, {0, 1}, {-1, 0}, {0, -1}};
while (!q.isEmpty()) {
int size = q.size();
boolean flag = false;
for (int i = 0; i < size; i++) {
int[] current = q.poll();
int row2 = current[0];
int col2 = current[1];
for (int[] dierction : directions) {
int newrow = row2 + dierction[0];
int newcol = col2 + dierction[1];
if (newrow >= 0 && newrow < row && newcol >= 0 && newcol < col && grid[newrow][newcol] == 1) {
grid[newrow][newcol] = 2;
q.offer(new int[]{newrow, newcol});
freashOrange--;
flag = true;
}
}
}
if (flag) {
time++;
}
}
return freashOrange == 0 ? time : -1;
}
}
总结
DFS 在一些小规模输入下,或者在某些实现中,可能表现得比 BFS 快一些,这是因为 DFS 的递归实现更简洁,
常数因子较小,栈操作的开销可能小于队列操作的开销。
BFS 是更适合腐烂橘子问题的解决方案,因为它符合逐层扩散的自然过程,并且能够保证腐烂橘子传播到每个新鲜橘子的最短时间。
因此,虽然 DFS 可能会在某些特定情况下表现得更快,但对于这道题,BFS 是更加直观、正确且鲁棒的选择。