DFS(深度优先遍历)

本文介绍了深度优先遍历(DFS)的概念及其在二叉树和网格结构中的实现。二叉树的DFS通过访问左、右子节点进行,而网格中的DFS则需要考虑四个相邻格子。为了避免重复遍历,需要对已访问的节点进行标记。此外,还提供了求解岛屿数量、岛屿最大面积等实际问题的DFS应用实例。
摘要由CSDN通过智能技术生成

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:岛屿周长

深度优先搜索(DFS)是一种常用的图遍历算法,它可以用来遍历或搜索图中的节点。在DFS中,从一个起始节点开始,沿着一条路径尽可能深入地访问节点,直到无法继续深入为止,然后回溯到上一个节点,继续访问其他未被访问的节点,直到所有节点都被访问为止。 在Java中,可以使用递归或者栈来实现DFS算法。下面是一个使用递归实现DFS的示例代码: ```java import java.util.*; class Graph { private int V; // 图中节点的数量 private LinkedList<Integer> adj[]; // 邻接表表示图 // 构造函数 Graph(int v) { V = v; adj = new LinkedList[v]; for (int i = 0; i < v; ++i) adj[i] = new LinkedList(); } // 添加边 void addEdge(int v, int w) { adj[v].add(w); } // 递归实现DFS void DFSUtil(int v, boolean visited[]) { visited[v] = true; System.out.print(v + " "); Iterator<Integer> i = adj[v].listIterator(); while (i.hasNext()) { int n = i.next(); if (!visited[n]) DFSUtil(n, visited); } } // 对外公开的DFS接口 void DFS(int v) { boolean visited[] = new boolean[V]; DFSUtil(v, visited); } } public class Main { public static void main(String args[]) { Graph g = new Graph(4); g.addEdge(0, 1); g.addEdge(0, 2); g.addEdge(1, 2); g.addEdge(2, 0); g.addEdge(2, 3); g.addEdge(3, 3); System.out.println("节点2开始的DFS遍历结果:"); g.DFS(2); } } ``` 上述代码中,我们首先定义了一个`Graph`类来表示图,其中使用邻接表来存储图的结构。然后,我们实现了`DFSUtil`方法来递归地进行DFS遍历,并在遍历过中打印节点的值。最后,在`main`方法中创建一个图对象,并调用`DFS`方法来进行DFS遍历。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

智博的自留地

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值