如何建图
使用一个二维数组 存储所有可能到达的边. 是一个边的集合.
该图的建法 (伪代码)
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;
}
}