从二叉树到图-广度优先遍历 vs 深度优先遍历

350 阅读3分钟

概念

拿比较常见的二叉树来引出 广度优先遍历和深度优先遍历的概念。 上图是一个非常普通的二叉树,树是一种特殊的图,所以可以先搞懂树的遍历,然后再扩展到任意图上

树的广度优先遍历

树的广度优先遍历又叫做树的层次遍历,说白了就是一层一层的向下进行遍历,确实是先考虑遍历的广度的。

图示: 从图中可以明显的看到,我们在 树的层次 纬度上是 从上到下的,在每一层上则是从左到右的。 直接上代码:

private static void bfs(TreeNode root) {  
    if (root == null) {  
        return;  
    }  
    ArrayDeque<TreeNode> queue = new ArrayDeque<>();  
    queue.add(root);  
    while (!queue.isEmpty()) {  
        int sz = queue.size();  
        for (int i = 0; i < sz; i++) {  
            TreeNode poll = queue.poll();  
            System.out.print(poll.val);  
            System.out.print(" ");  
            // 将左边的子节点放入队列中  
            if (poll.left != null) {  
                queue.add(poll.left);  
            }  
            // 将右边的子节点放入队列中  
            if (poll.right != null) {  
                queue.add(poll.right);  
            }  
        }  
    }  
}

结果:

注意这里用到的 队列,在 广义的图的广度优先遍历时也会使用这个数据结构。

树的深度优先遍历

顾名思义,深度优先遍历就是先考虑先往深了找,具体的又分为了 先序遍历、中序遍历、后序遍历,拿先序遍历举例,看图:

其实dfs和回溯的题目是紧密相连的。 代码:

private static void dfs(TreeNode root) {  
    if (root== null) {  
        return;  
    }  
    System.out.print(root.val);  
    System.out.print(" ");  
    if (root.left != null) {  
        dfs(root.left);  
    }  
    if (root.right != null) {  
        dfs(root.right);  
    }  
}

图的广度优先遍历和深度优先遍历

看完了树,现在看更加一般的图,其实原理都是一样的。 直接看一道LeetCode的题:最短的桥

给你一个大小为 `n x n` 的二元矩阵 `grid` ,其中 `1` 表示陆地,`0` 表示水域。

**岛** 是由四面相连的 `1` 形成的一个最大组,即不会与非组内的任何其他 `1` 相连。`grid` 中 **恰好存在两座岛** 。

你可以将任意数量的 `0` 变为 `1` ,以使两座岛连接起来,变成 **一座岛** 。

返回必须翻转的 `0` 的最小数目。

本文主要介绍图的遍历,思路大概说一下,就不详细介绍了:题目规定了给出的输入是有两座岛的,所以我们就需要先使用广度优先遍历或者深度优先遍历找出其中的一座岛,然后再通过这座岛中的每一个元素做广度优先遍历,直到遇见1。

广度优先遍历找到第一个岛

public int shortestBridge(int[][] grid) {
        int n = grid.length;
        // 分别是原图中的点向 上、下、右、左做遍历(广度)
        int[][] dirs = {{-1, 0}, {1, 0}, {0, 1}, {0, -1}};
        // 存放第一个岛
        List<int[]> island = new ArrayList<int[]>();
        // 是不是很熟悉,和树的广度优先遍历一样,需要用到队列这个数据结构
        Queue<int[]> queue = new ArrayDeque<int[]>();

        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                if (grid[i][j] == 1) {
                    // 这里就开始广度优先遍历了,类比树中将root放入队列。
                    queue.offer(new int[]{i, j});
                    grid[i][j] = -1;
                    while (!queue.isEmpty()) {
                        int[] cell = queue.poll();
                        int x = cell[0], y = cell[1];
                        island.add(cell);
                        // 遍历当前节点的上下左右,如果也是属于这个岛就入队
                        for (int k = 0; k < 4; k++) {
                            int nx = x + dirs[k][0];
                            int ny = y + dirs[k][1];
                            if (nx >= 0 && ny >= 0 && nx < n && ny < n && grid[nx][ny] == 1) {
                                queue.offer(new int[]{nx, ny});
                                grid[nx][ny] = -1;
                            }
                        }
                    }
                    // ======= 这里往上的部分就是广度优先遍历找到第一个岛=======
                    //======== 这里往下就是遍历第一个岛上的每个元素,并在它们的基础上做广度遍历=======
                    for (int[] cell : island) {
                        queue.offer(cell);
                    }
                    int step = 0;
                    while (!queue.isEmpty()) {
                        int sz = queue.size();
                        for (int k = 0; k < sz; k++) {
                            int[] cell = queue.poll();
                            int x = cell[0], y = cell[1];
                            for (int d = 0; d < 4; d++) {
                                int nx = x + dirs[d][0];
                                int ny = y + dirs[d][1];
                                if (nx >= 0 && ny >= 0 && nx < n && ny < n) {
                                    // 没找到的话,就继续往下一层找
                                    if (grid[nx][ny] == 0) {
                                        queue.offer(new int[]{nx, ny});
                                        grid[nx][ny] = -1;
                                        // 相当于找到了第二个岛,返回答案
                                    } else if (grid[nx][ny] == 1) {
                                        return step;
                                    }
                                }
                            }
                        }
                        step++;
                    }
                }
            }
        }
        return 0;
    }

深度优先遍历找第一个岛

    public int shortestBridge(int[][] grid) {
        int n = grid.length;
        int[][] dirs = {{-1, 0}, {1, 0}, {0, 1}, {0, -1}};

        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                if (grid[i][j] == 1) {
                    Queue<int[]> queue = new ArrayDeque<int[]>();
                    dfs(i, j, grid, queue);
                    int step = 0;
                    // ==== 这里往下不用看,和上面的解法是一样的=====
                    while (!queue.isEmpty()) {
                        int sz = queue.size();
                        for (int k = 0; k < sz; k++) {
                            int[] cell = queue.poll();
                            int x = cell[0], y = cell[1];
                            for (int d = 0; d < 4; d++) {
                                int nx = x + dirs[d][0];
                                int ny = y + dirs[d][1];
                                if (nx >= 0 && ny >= 0 && nx < n && ny < n) {
                                    if (grid[nx][ny] == 0) {
                                        queue.offer(new int[]{nx, ny});
                                        grid[nx][ny] = -1;
                                    } else if (grid[nx][ny] == 1) {
                                        return step;
                                    }
                                }
                            }
                        }
                        step++;
                    }
                }
            }
        }
        return 0;
    }
    // 对比二叉树的先序遍历的递归写法,只不过这里不单单考虑左孩子和右孩子了,每个节点需要考虑四个方向上的儿子。
    public void dfs(int x, int y, int[][] grid, Queue<int[]> queue) {
        if (x < 0 || y < 0 || x >= grid.length || y >= grid[0].length || grid[x][y] != 1) {
            return;
        }
        queue.offer(new int[]{x, y});
        grid[x][y] = -1;
        dfs(x - 1, y, grid, queue);
        dfs(x + 1, y, grid, queue);
        dfs(x, y - 1, grid, queue);
        dfs(x, y + 1, grid, queue);
    }
}

至此,图的广度优先遍历和深度优先遍历就讲完了,图的部分可能没有描述的那么细节,建议去领悟一下这道题目,然后对比着二叉树进行理解,so easy !