DEMO
迪杰斯特拉算法
1、邻接矩阵
1 package LeetCode.test11_tu; 2 3 import java.util.Arrays; 4 5 /** 6 * 邻接矩阵(迪杰斯特拉算法) 7 */ 8 public class demo1 { 9 public int dij(int N, int M, int[][] res) { 10 int[][] graph = new int[N + 1][N + 1]; 11 boolean[] vis = new boolean[N + 1]; 12 int[] d = new int[N + 1]; 13 14 for (int i = 0; i <= N; i++) { 15 Arrays.fill(graph[i], Integer.MAX_VALUE); 16 } 17 for (int i = 0; i < M; i++) { 18 graph[res[i][0]][res[i][1]] = res[i][2]; 19 graph[res[i][1]][res[i][0]] = res[i][2]; 20 } 21 Arrays.fill(d,Integer.MAX_VALUE); 22 d[1] = 0; // 1到1的距离(起始点) 23 24 for (int i = 1; i <= N; i++) { 25 int u = -1, MIN = Integer.MAX_VALUE; 26 for (int j = 1; j <= N; j++) { //使d[u]最小的还未被访问的顶点的标号 27 if (!vis[j] && d[j] < MIN) { 28 u = j; 29 MIN = d[j]; 30 } 31 } 32 if (u == -1) { 33 continue; 34 } 35 vis[u] = true; 36 for (int v = 1; v <= N; v++) { 37 if (!vis[v] && graph[u][v] != Integer.MAX_VALUE && graph[u][v] + d[u] < d[v]) { 38 d[v] = graph[u][v] + d[u]; 39 } 40 } 41 } 42 return d[N]; 43 } 44 } 45 46 class Test_demo1 { 47 public static void main(String[] args) { 48 int N = 3, M = 3; // N为路口数 M为路数 标号为1的路口是商店所在地,标号为N的路口是赛场所在地 49 int[][] res = {{1, 2, 5}, {2, 3, 5}, {3, 1, 2}}; 50 // int N = 2, M = 1; 51 // int[][] res = {{1, 2, 3}}; 52 demo1 s = new demo1(); 53 System.out.println(s.dij(N, M, res)); 54 } 55 }
2、邻接表
1 package LeetCode.test11_tu; 2 3 import java.util.*; 4 5 /** 6 * 邻接表(迪杰斯特拉算法) 7 */ 8 9 class Node { 10 int v; 11 int dis; 12 13 public Node(int v, int dis) { 14 this.v = v; 15 this.dis = dis; 16 } 17 } 18 19 public class demo2 { 20 public int dij(int N, int M, int[][] res) { 21 boolean[] vis = new boolean[N + 1]; 22 int[] d = new int[N + 1]; 23 Map<Integer, List<Node>> map = new HashMap<>(); 24 25 for (int i = 1; i <= N; i++) { 26 map.put(i, new ArrayList<>()); 27 } 28 for (int i = 0; i < M; i++) { 29 map.get(res[i][0]).add(new Node(res[i][1], res[i][2])); 30 map.get(res[i][1]).add(new Node(res[i][0], res[i][2])); 31 } 32 Arrays.fill(d, Integer.MAX_VALUE); 33 d[1] = 0; 34 35 for (int i = 1; i <= N; i++) { 36 int u = -1, MIN = Integer.MAX_VALUE; 37 for (int j = 1; j <= N; j++) { 38 if (!vis[j] && d[j] < MIN) { 39 u = j; 40 MIN = d[j]; 41 } 42 } 43 if (u == -1) { 44 continue; 45 } 46 vis[u] = true; 47 for (Node next : map.get(u)) { 48 int v = next.v; 49 int dis = next.dis; 50 if (!vis[v] && dis + d[u] < d[v]) { 51 d[v] = dis + d[u]; 52 } 53 } 54 } 55 return d[N]; 56 } 57 } 58 59 class Test_demo2 { 60 public static void main(String[] args) { 61 // int N = 3, M = 3; // N为路口数 M为路数 62 // int[][] res = {{1, 2, 5}, {2, 3, 5}, {3, 1, 2}}; 63 int N = 2, M = 1; 64 int[][] res = {{1, 2, 3}}; 65 demo2 s = new demo2(); 66 System.out.println(s.dij(N, M, res)); 67 } 68 }
弗洛伊德算法
1 package LeetCode.test11_tu; 2 3 import java.util.Arrays; 4 5 /** 6 * 邻接矩阵(弗洛伊德算法) 7 */ 8 public class demo3 { 9 public int floyd(int N, int M, int[][] res) { 10 int[][] graph = new int[N + 1][N + 1]; 11 for (int i = 0; i <= N; i++) { 12 Arrays.fill(graph[i], Integer.MAX_VALUE); 13 } 14 for (int i = 0; i < M; i++) { 15 graph[res[i][0]][res[i][1]] = res[i][2]; 16 graph[res[i][1]][res[i][0]] = res[i][2]; 17 } 18 for (int k = 1; k <= N; k++) { 19 for (int i = 1; i <= N; i++) { 20 if (graph[i][k] != Integer.MAX_VALUE) { 21 for (int j = 1; j <= N; j++) { 22 if (graph[k][j] != Integer.MAX_VALUE && graph[i][j] > graph[i][k] + graph[k][j]) { 23 graph[i][j] = graph[i][k] + graph[k][j]; 24 } 25 } 26 } 27 } 28 } 29 return graph[1][N]; 30 } 31 } 32 33 class Test_demo3 { 34 public static void main(String[] args) { 35 // int N = 3, M = 3; // N为路口数 M为路数 36 // int[][] res = {{1, 2, 5}, {2, 3, 5}, {3, 1, 2}}; 37 int N = 2, M = 1; 38 int[][] res = {{1, 2, 3}}; 39 demo3 s = new demo3(); 40 System.out.println(s.floyd(N, M, res)); 41 } 42 }
一、二分图
1、判断二分图
问题:
存在一个 无向图 ,图中有 n 个节点。其中每个节点都有一个介于 0 到 n - 1 之间的唯一编号。给你一个二维数组 graph ,其中 graph[u] 是一个节点数组,由节点 u 的邻接节点组成。形式上,对于 graph[u] 中的每个 v ,都存在一条位于节点 u 和节点 v 之间的无向边。该无向图同时具有以下属性:
不存在自环(graph[u] 不包含 u)。
不存在平行边(graph[u] 不包含重复值)。
如果 v 在 graph[u] 内,那么 u 也应该在 graph[v] 内(该图是无向图)
这个图可能不是连通图,也就是说两个节点 u 和 v 之间可能不存在一条连通彼此的路径。
二分图 定义:如果能将一个图的节点集合分割成两个独立的子集 A 和 B ,并使图中的每一条边的两个节点一个来自 A 集合,一个来自 B 集合,就将这个图称为 二分图 。
如果图是二分图,返回 true ;否则,返回 false 。
示例 1:
输入:graph = [[1,2,3],[0,2],[0,1,3],[0,2]]
输出:false
解释:不能将节点分割成两个独立的子集,以使每条边都连通一个子集中的一个节点与另一个子集中的一个节点。
示例 2:
输入:graph = [[1,3],[0,2],[1,3],[0,2]]
输出:true
解释:可以将节点分成两组: {0, 2} 和 {1, 3} 。
1 package LeetCode.test11_tu; 2 3 import java.util.LinkedList; 4 import java.util.Queue; 5 6 /** 7 * 思路:对未染色的节点进行染色,并且检查是否有颜色相同的相邻节点存在。 8 * 用0表示未检查的节点,用1和2表示两种不同的颜色。 9 */ 10 public class ques_785_判断二分图 { 11 public boolean isBipartite(int[][] graph) { 12 int length = graph.length; 13 if (length == 0) { 14 return true; 15 } 16 int[] color = new int[length]; 17 Queue<Integer> queue = new LinkedList<>(); 18 for (int i = 0; i < length; i++) { 19 if (color[i] == 0) { 20 queue.offer(i); 21 color[i] = 1; 22 } 23 while (!queue.isEmpty()) { 24 Integer poll = queue.poll(); 25 for (Integer j : graph[poll]) { 26 if (color[j] == 0) { 27 queue.offer(j); 28 color[j] = (color[poll] == 1 ? 2 : 1); 29 } else if (color[poll] == color[j]) { 30 return false; 31 } 32 } 33 } 34 } 35 return true; 36 } 37 } 38 39 class Test_785 { 40 public static void main(String[] args) { 41 // int[][] graph = {{1, 2, 3}, {0, 2}, {0, 1, 3}, {0, 2}}; 42 int[][] graph = {{1, 3}, {0, 2}, {1, 3}, {0, 2}}; 43 ques_785_判断二分图 s = new ques_785_判断二分图(); 44 System.out.println(s.isBipartite(graph)); 45 } 46 }
二、拓扑排序
2、课程表II
问题:
现在你总共有 numCourses 门课需要选,记为 0 到 numCourses - 1。给你一个数组 prerequisites ,其中 prerequisites[i] = [ai, bi] ,表示在选修课程 ai 前 必须 先选修 bi 。
例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示:[0,1] 。
返回你为了学完所有课程所安排的学习顺序。可能会有多个正确的顺序,你只要返回 任意一种 就可以了。如果不可能完成所有课程,返回 一个空数组 。
示例 1:
输入:numCourses = 2, prerequisites = [[1,0]]
输出:[0,1]
解释:总共有 2 门课程。要学习课程 1,你需要先完成课程 0。因此,正确的课程顺序为 [0,1] 。
示例 2:
输入:numCourses = 4, prerequisites = [[1,0],[2,0],[3,1],[3,2]]
输出:[0,2,1,3]
解释:总共有 4 门课程。要学习课程 3,你应该先完成课程 1 和课程 2。并且课程 1 和课程 2 都应该排在课程 0 之后。
因此,一个正确的课程顺序是 [0,1,2,3] 。另一个正确的排序是 [0,2,1,3] 。
示例 3:
输入:numCourses = 1, prerequisites = []
输出:[0]
1 package LeetCode.test11_tu; 2 3 import java.util.*; 4 5 /** 6 * 思路:拓扑排序最经典的算法是Kahn算法。 7 * 首先,先拿出所有入度为0的点排在前面,并在原图中将它们删除; 8 * 这时有些点的入度减少了,于是再拿出当前所有入度为0的点放在已经排序的序列后面,然后删除; 9 * 因为是有向无环图,而且删除操作不会产生环,所以每时每刻都一定存在入度为0的点,一定可以不断进行下去,直到所有点被删除。 10 */ 11 public class ques_210_课程表II { 12 List<List<Integer>> graph; // 图 13 int[] inDegree; // 度 14 int[] result; 15 int index; 16 17 public int[] findOrder(int numCourses, int[][] prerequisites) { 18 graph = new ArrayList<>(); 19 for (int i = 0; i < numCourses; i++) { 20 graph.add(new ArrayList<>()); 21 } 22 inDegree = new int[numCourses]; 23 result = new int[numCourses]; 24 index = 0; 25 26 for (int[] pre : prerequisites) { 27 graph.get(pre[1]).add(pre[0]); // [[1, 2], [3], [3], []] 28 inDegree[pre[0]]++; // [0, 1, 1, 2] 29 } 30 Queue<Integer> queue = new LinkedList<>(); 31 for (int i = 0; i < numCourses; i++) { 32 if (inDegree[i] == 0) { 33 queue.offer(i); 34 } 35 } 36 while (!queue.isEmpty()) { 37 Integer top = queue.poll(); 38 result[index++] = top; 39 for (Integer t : graph.get(top)) { // [1, 2] 40 inDegree[t]--; 41 if (inDegree[t] == 0) { 42 queue.offer(t); 43 } 44 } 45 } 46 if (index != numCourses) { 47 return new int[0]; 48 } 49 return result; 50 } 51 } 52 53 class Test_210 { 54 public static void main(String[] args) { 55 int numCourses = 4; 56 int[][] prerequisites = {{1, 0}, {2, 0}, {3, 1}, {3, 2}}; 57 ques_210_课程表II s = new ques_210_课程表II(); 58 System.out.println(Arrays.toString(s.findOrder(numCourses, prerequisites))); 59 } 60 }
三、最小生成树
并查集
package LeetCode.test11_tu; /** * 主题:Union-Find算法(并查集) * ps:https://segmentfault.com/a/1190000038593250 https://segmentfault.com/a/1190000038594253 * 1、用parent数组记录每个节点的父节点,相当于指向父节点的指针,所以parent数组内实际存储着一个森林(若干棵多叉树)。 * 2、用size数组记录着每棵树的重量,目的是让union后树依然拥有平衡性,而不会退化成链表,影响操作效率。 * 3、在find函数中进行路径压缩,保证任意树的高度保持在常数,使得union和connected API时间复杂度为O(1)。 */ public class UF { private int count; // 连通分量个数 private int[] parent; //存储一棵树 private int[] size; //记录树的重量(小一些的树接到大一些的树下面) public UF(int n) { this.count = n; parent = new int[n]; size = new int[n]; for (int i = 0; i < n; i++) { parent[i] = i; size[i] = i; } } public int find(int x) { while (parent[x] != x) { parent[x] = parent[parent[x]]; // 路径压缩 x = parent[x]; } return x; } public void union(int p, int q) { int rootP = find(p); int rootQ = find(q); if (rootP == rootQ) { //产生环 return; } if (size[rootP] > size[rootQ]) { parent[rootQ] = rootP; size[rootP] += size[rootQ]; } else { parent[rootP] = rootQ; size[rootQ] += size[rootP]; } count--; } public boolean connected(int p, int q) { int rootP = find(p); int rootQ = find(q); return rootP == rootQ; } public int count(){ return count; } }
3、最低成本联通所有城市
问题:
想象一下你是个城市基建规划者,地图上有 N 座城市,它们按以 1 到 N 的次序编号。
给你一些可连接的选项 conections,其中每个选项 conections[i] = [city1, city2, cost] 表示将城市 city1 和城市 city2 连接所要的成本。(连接是双向的,也就是说城市 city1 和城市 city2 相连也同样意味着城市 city2 和城市 city1 相连)。
返回使得每对城市间都存在将它们连接在一起的连通路径(可能长度为 1 的)最小成本。该最小成本应该是所用全部连接代价的综合。如果根据已知条件无法完成该项任务,则请你返回 -1。
示例 1:
输入:N = 3, conections = [[1,2,5],[1,3,6],[2,3,1]]
输出:6
解释:
选出任意 2 条边都可以连接所有城市,我们从中选取成本最小的 2 条。
示例 2:
输入:N = 4, conections = [[1,2,3],[3,4,4]]
输出:-1
解释:
即使连通所有的边,也无法连接所有城市。
1 package LeetCode.test11_tu; 2 3 import java.util.Arrays; 4 5 /** 6 * 本题考察最小生成树(minimum spanning tree, MST)的求法: 7 * (1)Prim’s Algorithm:利用优先队列选择最小的消耗 8 * (2)Kruskal’s Algorithm,排序后使用并查集。 9 */ 10 public class ques_1135_最低成本联通所有城市 { 11 public int minimumCost(int N, int[][] connections) { 12 // -------------第一种方法----------- 13 int[][] graph = new int[N + 1][N + 1]; 14 boolean[] vis = new boolean[N + 1]; 15 int[] d = new int[N + 1]; 16 17 for (int[] conn : connections) { 18 graph[conn[0]][conn[1]] = conn[2]; 19 graph[conn[1]][conn[0]] = conn[2]; 20 } 21 for (int i = 1; i <= N; i++) { 22 d[i] = Integer.MAX_VALUE; 23 } 24 d[1] = 0; 25 // return prim(N, graph, vis, d); 26 27 // -------------第二种方法----------- 28 return Kruskal(N, connections); 29 30 } 31 32 public int Kruskal(int N, int[][] connections) { 33 UF uf = new UF(N + 1); 34 Arrays.sort(connections,(a,b)->(a[2]-b[2])); 35 int num = 0; // 记录权重 36 for (int[] edge : connections) { 37 int u = edge[0]; 38 int v = edge[1]; 39 int weight = edge[2]; 40 if (uf.connected(u, v)) { 41 continue; 42 } 43 num += weight; 44 uf.union(u,v); 45 } 46 47 // 节点0没有被使用,所以0会额外占用一个连通分量 48 return uf.count()==2?num:-1; 49 } 50 51 public int prim(int N, int[][] graph, boolean[] vis, int[] d) { 52 // Prim算法解决的问题是连通无向有权图中最小生成树问题,而Dijkstra算法解决的问题是源点到目标点的最短路径问题。 53 for (int i = 1; i <= N; i++) { 54 int u = -1; 55 int MIN = Integer.MAX_VALUE; 56 for (int j = 1; j <= N; j++) { 57 if (!vis[j] && d[j] < MIN) { 58 u = j; 59 MIN = d[u]; 60 } 61 } 62 if (u == -1) { 63 continue; 64 } 65 vis[u] = true; 66 for (int v = 1; v <= N; v++) { 67 if (!vis[v] && graph[u][v] < d[v]) { 68 d[v] = graph[u][v]; 69 } 70 } 71 } 72 int ans = 0; 73 for (int i = 1; i <= N; i++) { 74 ans += d[i]; 75 } 76 return ans != 0 ? ans : -1; 77 } 78 } 79 80 81 class Test_1135 { 82 public static void main(String[] args) { 83 // int[][] connections = {{1, 2, 5}, {1, 3, 6}, {2, 3, 1}}; 84 // int N = 3; 85 86 // int[][] connections = {{1,2,3},{3,4,4}}; 87 // int N = 4; 88 89 // int[][] connections = {{1, 2, 1}, {1, 3, 2}, {2, 3, 4}}; 90 // int N = 3; 91 92 int[][] connections = {{1, 2, 1}, {1, 3, 4}, {1, 4, 1}, 93 {2, 3, 3}, {2, 4, 2}, {3, 4, 5}}; 94 int N = 4; 95 96 ques_1135_最低成本联通所有城市 s = new ques_1135_最低成本联通所有城市(); 97 System.out.println(s.minimumCost(N, connections)); 98 // 6 -1 3 5 99 } 100 }
4、冗余连接
问题:
树可以看成是一个连通且 无环 的 无向 图。
给定往一棵 n 个节点 (节点值 1~n) 的树中添加一条边后的图。添加的边的两个顶点包含在 1 到 n 中间,且这条附加的边不属于树中已存在的边。图的信息记录于长度为 n 的二维数组 edges ,edges[i] = [ai, bi] 表示图中在 ai 和 bi 之间存在一条边。
请找出一条可以删去的边,删除后可使得剩余部分是一个有着 n 个节点的树。如果有多个答案,则返回数组 edges 中最后出现的边。
示例 1:
输入: edges = [[1,2], [1,3], [2,3]]
输出: [2,3]
示例 2:
输入: edges = [[1,2], [2,3], [3,4], [1,4], [1,5]]
输出: [1,4]
1 package LeetCode.test11_tu; 2 3 import java.util.Arrays; 4 5 public class ques_684_冗余连接 { 6 public int[] findRedundantConnection(int[][] edges) { 7 int n = edges.length; 8 UF uf = new UF(n+1); 9 for (int[] edge:edges) { 10 int u = edge[0]; 11 int v = edge[1]; 12 if (uf.connected(u,v)){ 13 return edge; 14 } 15 uf.union(u,v); 16 } 17 return new int[0]; 18 } 19 } 20 21 class Test_684 { 22 public static void main(String[] args) { 23 int[][] edges = {{1, 2}, {1, 3}, {2, 3}}; 24 ques_684_冗余连接 s = new ques_684_冗余连接(); 25 System.out.println(Arrays.toString(s.findRedundantConnection(edges))); 26 } 27 }
四、图的遍历
5、从始点到终点的所有路径
问题:
给定有向图的边 edges,以及该图的始点 source 和目标终点 destination,确定从始点 source 出发的所有路径是否最终结束于目标终点 destination,即:
从始点 source 到目标终点 destination 存在至少一条路径
如果存在从始点 source 到没有出边的节点的路径,则该节点就是路径终点。
从始点source到目标终点 destination 可能路径数是有限数字
当从始点 source 出发的所有路径都可以到达目标终点 destination 时返回 true,否则返回 false。
示例 1:
输入:n = 3, edges = [[0,1],[0,2]], source = 0, destination = 2
输出:false
说明:节点 1 和节点 2 都可以到达,但也会卡在那里。
示例 2:
输入:n = 4, edges = [[0,1],[0,3],[1,2],[2,1]], source = 0, destination = 3
输出:false
说明:有两种可能:在节点 3 处结束,或是在节点 1 和节点 2 之间无限循环。
示例 3:
输入:n = 4, edges = [[0,1],[0,2],[1,3],[2,3]], source = 0, destination = 3
输出:true
示例 4:
输入:n = 3, edges = [[0,1],[1,1],[1,2]], source = 0, destination = 2
输出:false
说明:从始点出发的所有路径都在目标终点结束,
但存在无限多的路径,如 0-1-2,0-1-1-2,0-1-1-1-2,0-1-1-1-1-2 等。
示例 5:
输入:n = 2, edges = [[0,1],[1,1]], source = 0, destination = 1
输出:false
说明:在目标节点上存在无限的自环。
1 package LeetCode.test11_tu; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 6 /** 7 * 虽然使用深度优先搜索可以解决大部分的图遍历问题,但是注意判断是否陷入了环路。 8 * 终点只有一个,且没有环!!! 9 */ 10 public class ques_1059_从始点到终点的所有路径 { 11 public boolean leadsToDestination(int n, int[][] edges, int source, int destination) { 12 boolean[] visited = new boolean[n]; 13 List<List<Integer>> graph = new ArrayList<>(); 14 for (int i = 0; i < n; i++) { 15 graph.add(new ArrayList<>()); 16 } 17 for (int[] edge : edges) { 18 graph.get(edge[0]).add(edge[1]); 19 20 } 21 if (!graph.get(destination).isEmpty()) { //终点后面还有路径 22 return false; 23 } 24 return dfs(graph, visited, source, destination); 25 } 26 27 public boolean dfs(List<List<Integer>> graph, boolean[] visited, int cur, int destination) { 28 if (graph.get(cur).size() == 0 && cur != destination) { //到达一个终点,但不是目标点 29 return false; 30 } 31 for (Integer next:graph.get(cur)) { // 访问过了(有环) 32 if (visited[next]){ 33 return false; 34 } 35 visited[next] = true; 36 if(!dfs(graph,visited,next,destination)){ 37 return false; 38 } 39 visited[next] = false; // 回溯 40 } 41 return true; 42 } 43 } 44 45 class Test_1059 { 46 public static void main(String[] args) { 47 // int n = 3; 48 // int[][] edges = {{0, 1}, {0, 2}}; 49 // int source = 0; 50 // int destination = 2; 51 52 // int n = 4; 53 // int[][] edges = {{0, 1}, {0, 3}, {1, 2}, {2, 1}}; 54 // int source = 0; 55 // int destination = 3; 56 57 int n = 4; 58 int[][] edges = {{0, 1}, {0, 2}, {1, 3}, {2, 3}}; 59 int source = 0; 60 int destination = 3; 61 62 ques_1059_从始点到终点的所有路径 s = new ques_1059_从始点到终点的所有路径(); 63 System.out.println(s.leadsToDestination(n, edges, source, destination)); 64 // false false true 65 } 66 }