DFS(深度优先遍历)
二叉树的DFS模板
void traverse(TreeNode root) {
// 判断 base case
if (root == null) {
return;
}
// 访问两个相邻结点:左子结点、右子结点
traverse(root.left);
traverse(root.right);
}
二叉树的 DFS 有两个要素:「访问相邻结点」和「判断 base case」。
第一个要素是访问相邻结点。二叉树的相邻结点非常简单,只有左子结点和右子结点两个。二叉树本身就是一个递归定义的结构:一棵二叉树,它的左子树和右子树也是一棵二叉树。那么我们的 DFS 遍历只需要递归调用左子树和右子树即可。
第二个要素是 判断 base case。一般来说,二叉树遍历的 base case 是 root == null 这样一个条件判断其实有两个含义:一方面,这表示 root 指向的子树为空,不需要再往下遍历了。另一方面,在 root == null 的时候及时返回,可以让后面的 root.left 和 root.right 操作不会出现空指针异常。
网格中的DFS
网格中的DFS有四个方向,所以相当于四叉树,base case 就是超出网格的边界。
对于格子 (r, c) 来说(r 和 c 分别代表行坐标和列坐标)。
四个相邻的格子分别是 (r-1, c)、(r+1, c)、(r, c-1)、(r, c+1)。换句话说,网格结构是「四叉」的。
甭管当前是在哪个格子,先往四个方向走一步再说,如果发现走出了网格范围再赶紧返回。这跟二叉树的遍历方法是一样的,先递归调用,发现 root == null 再返回。
这样,我们得到了网格 DFS 遍历的框架代码:
void dfs(int[][] grid, int r, int c) {
// 判断 base case
// 如果坐标 (r, c) 超出了网格范围,直接返回
if (!inArea(grid, r, c)) {
return;
}
// 访问上、下、左、右四个相邻结点
dfs(grid, r - 1, c);
dfs(grid, r + 1, c);
dfs(grid, r, c - 1);
dfs(grid, r, c + 1);
}
// 判断坐标 (r, c) 是否在网格中
boolean inArea(int[][] grid, int r, int c) {
return 0 <= r && r < grid.length
&& 0 <= c && c < grid[0].length;
}
如何避免重复遍历
网格结构的 DFS 与二叉树的 DFS 最大的不同之处在于,遍历中可能遇到遍历过的结点。这是因为,网格结构本质上是一个「图」,我们可以把每个格子看成图中的结点,每个结点有向上下左右的四条边。在图中遍历时,自然可能遇到重复遍历结点。
这时候,DFS 可能会不停地「兜圈子」
标记已经遍历过的格子。以岛屿问题为例,我们需要在所有值为 1 的陆地格子上做 DFS 遍历。每走过一个陆地格子,就把格子的值改为 2,这样当我们遇到 2 的时候,就知道这是遍历过的格子了。也就是说,每个格子可能取三个值:
0 —— 海洋格子
1 —— 陆地格子(未遍历过)
2 —— 陆地格子(已遍历过)
在框架代码中加入标记已遍历的代码:
void dfs(int[][] grid, int r, int c) {
// 判断 base case
if (!inArea(grid, r, c)) {
return;
}
// 如果这个格子不是岛屿,直接返回
if (grid[r][c] != 1) {
return;
}
grid[r][c] = 2; // 将格子标记为「已遍历过」
// 访问上、下、左、右四个相邻结点
dfs(grid, r - 1, c);
dfs(grid, r + 1, c);
dfs(grid, r, c - 1);
dfs(grid, r, c + 1);
}
// 判断坐标 (r, c) 是否在网格中
boolean inArea(int[][] grid, int r, int c) {
return 0 <= r && r < grid.length
&& 0 <= c && c < grid[0].length;
}
练习题:
1、岛屿数量力扣200:岛屿的数量
/**
* 判断岛屿数量
*/
public class DFS {
public void dfs(int[][] grid, int r, int c) {
// 判断 base case
if (!inArea(grid, r, c)) {
return;
}
// 如果这个格子不是岛屿,直接返回
if (grid[r][c] != 1) {
return;
}
grid[r][c] = 2; // 将格子标记为「已遍历过」
// 访问上、下、左、右四个相邻结点
dfs(grid, r - 1, c);
dfs(grid, r + 1, c);
dfs(grid, r, c - 1);
dfs(grid, r, c + 1);
}
// 判断坐标 (r, c) 是否在网格中
public boolean inArea(int[][] grid, int r, int c) {
return 0 <= r && r < grid.length
&& 0 <= c && c < grid[0].length;
}
//求岛屿数量
public int numofIsland(int[][] grid){
int res = 0;
for(int r=0;r<grid.length;r++){
for(int c=0;c<grid[0].length;c++){
if(grid[r][c]==1){
dfs(grid,r,c);
res++;
}
}
}
return res;
}
public static void main(String[] args){
int[][] grid= {
{1, 1, 1, 0, 1},
{1, 1, 0, 0, 0},
{1, 1, 0, 1, 0},
{0, 0, 0, 0, 1}};
DFS d = new DFS();
int num = d.numofIsland(grid);
System.out.println("岛屿数量为:"+num);
}
}
2、岛屿最大面积力扣695:岛屿的最大面积
/**
* 岛屿最大面积
*/
class MaxArea{
public int dfs(int[][] grid, int r, int c) {
// 判断 base case
if (!inArea(grid, r, c)) {
return 0;
}
// 如果这个格子不是岛屿,直接返回
if (grid[r][c] != 1) {
return 0;
}
grid[r][c] = 2; // 将格子标记为「已遍历过」
// 访问上、下、左、右四个相邻结点
return 1+
dfs(grid, r - 1, c)+
dfs(grid, r + 1, c)+
dfs(grid, r, c - 1)+
dfs(grid, r, c + 1);
}
// 判断坐标 (r, c) 是否在网格中
public boolean inArea(int[][] grid, int r, int c) {
return 0 <= r && r < grid.length
&& 0 <= c && c < grid[0].length;
}
public int MaxAreaofIsland(int[][] grid){
int res = 0;
for(int r=0;r<grid.length;r++){
for(int c=0;c<grid[0].length;c++){
if(grid[r][c]==1){
int a = dfs(grid,r,c);
res = Math.max(a,res);
}
}
}
return res;
}
public static void main(String[] args){
int[][] grid= {
{1, 1, 1, 0, 1},
{1, 1, 0, 0, 0},
{1, 1, 0, 1, 0},
{0, 0, 0, 0, 1}};
MaxArea d = new MaxArea();
int num = d.MaxAreaofIsland(grid);
System.out.println("岛屿最大面积:"+num);
}
}
3、人工岛屿力扣827困难:人工岛屿面积
4、岛屿周长力扣463:岛屿周长