图的深搜、广搜 复习

175 阅读3分钟

如何建图

使用一个二维数组 存储所有可能到达的边. 是一个边的集合.

该图的建法 (伪代码)

     List<List> edges = new ArrayList<>();
//   遍历所有点 可能到达的边 具体问题具体分析
     edges.get(A).Add(B);
     edges.get(A).Add(E);
     // 无向图反向
     edges.get(B).add(A);
     edges.get(E).add(A);

实战

dfs : 不撞南墙不回头

bfs : 当前层全扫了

lc 684 冗余链接

思路 : 该题的本质就是 问图中是否有环. 1 -- 2 无环 , 1 -- 3 无环 . 2 -- 3 有环 输出

确保返回 edges 最后出现的边 我们采用 加一条边 判一次环.

如何判断是否有环?

使用一个[全局变量] (因为他在递归的过程不会改变),记录哪些点已经被访问过.但是由于无向图的限制,我从父亲点到儿子点,儿子点遍历所有边的的时候也可以回去,所以访问到父亲边 是不算环的 , 如果访问到父亲之外的点 就是形成环了,直接返回即可.

递归参数的确定 : 父亲 (局部可变,放入参数列表),当前遍历到哪个点了(局部可变,放入参数列表)

ac代码
class Solution {
    List<List<Integer>> edges = new ArrayList<>();
    boolean visit[];
    boolean hasCyle;
    public int[] findRedundantConnection(int[][] list) {
        int n = list.length;
        visit = new boolean[n + 1];
        for (int i = 0 ; i <= n ; i ++) {
            edges.add(new ArrayList<>());
        }
        
        
        for (int[] edge : list) {
            int x = edge[0];
            int y = edge[1];
            edges.get(x).add(y);
            edges.get(y).add(x);
            dfs(-1,x);
            if (hasCyle) return edge;
        }
        return null;
    }

     void dfs(int f , int x) {
        visit[x] = true;
        for (Integer next : edges.get(x)) {
            // System.out.println("next   " + next  + " f "  +  x);
            // System.out.println(Arrays.toString(visit));
            if (next == f) continue;
            if (visit[next]) { hasCyle = true;}
            else dfs(x,next);
        }
        visit[x] = false;
    }
}

lc207课程表

思路 : 想学 课程0 必须先完成课程 1 , 可以抽象为 0 -> 1 , 若在给出 1 ->0, 0依赖1 , 1 依赖 0 ,则产生了循环依赖问题. 链式依赖问题 我们可以抽象为图表示. 且这个图是有向图.

如何解决这种有依赖关系的图问题呢 ? 拓扑排序.

实现拓扑排序的算法有两种 : 1. bfs 消除入度法 2. dfs 点次 判断法(拓扑序小的 dfs 递归遍历的点次一定高于拓扑序大的).

如果 课程完成 不了 , 则表示 出现了 循环依赖 , 如何通过拓扑序判断这种循环 依赖的关系呢? 记录点是否都进行过一次入队操作,若 所有点都进行了一次入队操作,则没有产生循环依赖,反之. (因为入度不为0的点 , 我们不让他入

ac代码

class Solution {
    List<List<Integer>> graph = new ArrayList<>();
    int[] inDegree;
    int n;
    public boolean canFinish(int numCourses, int[][] prerequisites) {
        if ( prerequisites.length == 0) return true;
        n = numCourses;
        // 初始化 空间
        inDegree = new int[n];
        for (int i = 0;  i <  n ; i ++) graph.add(new ArrayList<>());
        buildGraph(prerequisites);
        // 进行广搜 消除课程入度
        Deque<Integer> que = new LinkedList<>();
        // 入度为0 入队
        for (int i = 0 ; i < n ; i ++) if (inDegree[i] == 0) que.add(i);

        // 广搜模板
        int cnt = 0;
        while (!que.isEmpty()) {
            int idx = que.removeFirst();
            cnt ++;
            List<Integer> next = graph.get(idx);
            for (int i : next) {
                inDegree[i] --;
                if (inDegree[i] == 0)
                    que.add(i);
            }
        }
        return cnt == n ;
    }


//  建图函数
    void buildGraph(int[][] edges) {
        for (int[] e : edges) {
            int study = e[0];
            int pre = e[1];
            // 有向图建立 单边集合
            graph.get(pre).add(study);
            // 入度表的更新
            inDegree[study] += 1;
        }
    }
}

AC 844 走迷宫

  • 思路 : 从 (1,1) 点位出发 , 上下左右任意一个方向(方向数组技巧), 至少 BFS 通常用在最短路。
  • 抽象 : 每次 不能走已经走过的点,则【不可能有环】,BFS 层序遍历维护 路径最小值即可。
代码实现
import java.util.*;
import java.io.*;

public class Main {
    static int[][] map;
    static int[] dx = {0,-1,0,1};
    static int[] dy = {1,0,-1,0};
    static boolean[][] visit ;
    public static void main(String[] args) {
        Scanner sc = new Scanner(new BufferedInputStream(System.in));
        int n = sc.nextInt();
        int m = sc.nextInt();
        map = new int[n + 1][m + 1];
        visit = new boolean[n + 1][m + 1];
        for (int i = 1 ; i <= n ; i ++)
            for (int j = 1 ; j <= m ; j ++)
                map[i][j] = sc.nextInt();

        System.out.println(bfs(1,1));
    }

    // bfs 模板应用
    static int bfs(int curx , int cury) {
        Deque<int[]> que = new LinkedList<>();
        // 0 表示最小步数
        que.add(new int[]{curx,cury,0});
//        int step = 0;
        while (!que.isEmpty()) {
            int[] info = que.removeFirst();
            int x = info[0];
            int y = info[1];
             int step = info[2];
            // 方向数组
            for (int i = 0 ; i < 4 ; i ++) {
                int nx = x + dx[i];
                int ny = y + dy[i];
                // 访问数组好习惯 判断合法性
                if (nx < 1 || nx >= map.length || ny < 1 || ny >= map[nx].length || map[nx][ny] == 1 || visit[nx][ny] )
                    continue;
                que.add(new int[]{nx,ny,step + 1});
                if (nx == map.length - 1 && ny == map[nx].length - 1)
                        return  step + 1;
                visit[nx][ny] = true;
            }

        }
        return -1;
    }
}
  • 暴搜 回溯法
  • 参数确定 :每层所在的 【点坐标、步数】 随递归变化而变化,作为局部变量。 是否以访问 则作为全局变量,用于维护 【当前点是否被遍历过】,暴搜每个点都被遍历 多次,复杂度 太高。
  • 确定终止条件 : 何时不搜 -- 到了数组边界 或者到了 m 、n 不搜 .
代码(只能过样例)
import java.util.*;
import java.io.*;

public class Main {
    static int[][] map;
    static int[] dx = {0,-1,0,1};
    static int[] dy = {1,0,-1,0};
    static boolean[][] visit ;
    static int min = Integer.MAX_VALUE;
    public static void main(String[] args) {
        Scanner sc = new Scanner(new BufferedInputStream(System.in));
        int n = sc.nextInt();
        int m = sc.nextInt();
        map = new int[n + 1][m + 1];
        visit = new boolean[n + 1][m + 1];
        for (int i = 1 ; i <= n ; i ++)
            for (int j = 1 ; j <= m ; j ++)
                map[i][j] = sc.nextInt();
        dfs(1,1,0);
        System.out.println(min);
    }

    // 回溯模板应用
    static void dfs(int curx , int cury, int step) {
        if (curx == map.length - 1 && cury == map[curx].length - 1){
            min = Math.min(step,min);
            return;
        }
        visit[curx][cury] = true;
        for (int i = 0 ; i < 4 ; i ++) {
            int nx = curx + dx[i];
            int ny = cury + dy[i];
            if(check(nx,ny)){
                // System.out.println("nx  " + nx + " ny " + ny);
                dfs(nx,ny,step + 1);
            }
        }
        visit[curx][cury] = false;
    }
    
   static boolean check(int x , int y) {
        if (x < 1 || x >= map.length || y < 1 || y >= map[x].length || map[x][y] == 1 || visit[x][y])
            return false;
        return true;
    }
}
AC 847 图中点的层次
  • 思路 : 简化版的走迷宫,多的是自己建图 。 注意 重边和自环。
代码
import java.util.*;
import java.io.*;

public class Main {
    static List<List<Integer>> graph = new ArrayList<>();
    static int n;
    //防止重边和自环
    static boolean[] visit;
    static int[] distance;
    public static void main(String[] args) {
        Scanner sc = new Scanner(new BufferedInputStream(System.in));
         n = sc.nextInt();
        int m = sc.nextInt();
        distance = new int[ n + 1];
        for (int i = 0 ; i <= n ; i ++) graph.add(new ArrayList<>());
        visit = new boolean[n + 1];
        while (m -- > 0) {
            int a = sc.nextInt();
            int b = sc.nextInt();
            // 有向图 建立单边
            graph.get(a).add(b);
        }
        System.out.println(bfs(1));
    } 
    
    static int bfs(int x) {
        if (x == n) return 0;
        Deque<int[]> que = new LinkedList<>();
        que.add(new int[]{x,0});
        while (!que.isEmpty()) {
            int[] info = que.removeFirst();
            int curx = info[0];
            int dis = info[1];
            visit[curx] = true;
            List<Integer> edges = graph.get(curx);
            for (int i : edges) {
                if (visit[i]) continue;
                if (i == n) return dis + 1;
                que.add(new int[]{i,dis + 1});
            }
        }
          return -1;
       
    }
}