最小生成树(MST)
朴素Prim算法(普利姆)
-
无向图,适合于稠密图, 时间复杂度O(n^2)
-
第一步,选距离生成树最近节点
-
第二步,最近节点加入生成树
-
第三步,更新非生成树节点到生成树的距离(即更新minDist数组)
-
一般做法是把合适的点放入集合,每次都遍历点作为顶点。
-
代码随想录给出的方案是minDist存储,这样每次只需要遍历一次顶点,把最小边放入数组,而无需遍历所有集合内的点作为顶点。
输入:
7 11
1 2 1
1 3 1
1 5 2
2 6 1
2 4 2
2 3 2
3 4 1
4 5 1
5 6 2
5 7 1
6 7 1import java.util.Arrays;
import java.util.Scanner;
public class Main {public static void main(String[] args) { Scanner in = new Scanner(System.in); int V = in.nextInt(); int E = in.nextInt(); // 初始化一个二维数组来存储图的边权重,初始化为最大值。 int[][] grid = new int[V + 1][V + 1]; for (int i = 0; i <= V; i++) { Arrays.fill(grid[i], Integer.MAX_VALUE); } //更新非生成树节点到生成树的距离 int[] minDist = new int[V + 1]; Arrays.fill(minDist, Integer.MAX_VALUE); //V是节点的数量,E是边的数量 for (int i = 1; i <= E; i++) { int v1 = in.nextInt(); int v2 = in.nextInt(); int val = in.nextInt(); grid[v1][v2] = val;// 更新边的权重 grid[v2][v1] = val; } boolean[] visited = new boolean[V + 1]; // 用于标记节点是否被访问过 minDist[1] = 0; //起始节点1,到自身的距离为0 //遍历每一个节点 for(int i = 1; i <= V; i++) { int nextNode = -1; //先遍历minDist找到未访问的节点中距离起始节点最近的节点 int minVal = Integer.MAX_VALUE; for(int k=1; k <= V; k++) { // 找到未访问的节点中距离起始节点最近的节点 if (!visited[k] && minVal> minDist[k]) { minVal = minDist[k]; nextNode = k; } } visited[nextNode] = true; // 标记下一个节点为已访问 //首先先更新当前节点到其他节点的距离 for(int j = 1 ;j<=V ;j++){ int val = grid[nextNode][j]; // 如果节点j已经被访问过,跳过 // 如果边的权重是最大值,表示没有边相连,跳过 if (visited[j] || val == Integer.MAX_VALUE) continue; // 如果当前节点到其他节点的距离小于已知最小距离,则更新最小距离 if (minDist[j] > val){ minDist[j] = val; // 更新最小距离 } } } // 统计结果 int result = 0; for (int i = 2; i <= V; i++) { result += minDist[i]; } System.out.println(result); }}
Kruskal算法(克鲁斯卡尔)
-
无向图,适合于稀疏图,时间复杂度 O (E logE)。
-
思路:( 排序边+并查集 ),边放入集合中排序,遍历边,并用并查集判断边是否是成环的边,如果是就跳过。当边达到n-1时可以提前结束
输入:
7 11
1 2 1
1 3 1
1 5 2
2 6 1
2 4 2
2 3 2
3 4 1
4 5 1
5 6 2
5 7 1
6 7 1import java.util.*;
public class Main{public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int V = scanner.nextInt(); int E = scanner.nextInt(); List<Edge> edges = new ArrayList<>(); int result_val = 0; for (int i = 0; i < E; i++) { int v1 = scanner.nextInt(); int v2 = scanner.nextInt(); int val = scanner.nextInt(); edges.add(new Edge(v1, v2, val)); } edges.sort(Comparator.comparingInt(e -> e.val)); // 按边的权重排序 UnionFind uf = new UnionFind(V); int num =0; for(Edge edge : edges) { // 如果已经选取的边数等于V-1,则停止 if(num==V-1)break; int rootS = uf.find(edge.s); int rootT = uf.find(edge.t); if (rootS == rootT) { continue; // 如果两个节点已经在同一集合中,则跳过 } uf.union(rootS, rootT); // 合并两个节点的集合 result_val += edge.val; // 累加边的权重 num++; } System.out.println(result_val); } static class Edge{ int s; // 起点 int t; // 终点 int val; // 边的权重 public Edge(int s, int t, int val) { this.s = s; this.t = t; this.val = val; } } static class UnionFind{ int[] parent; public UnionFind(int size) { parent = new int[size + 1]; for (int i = 1; i <= size; i++) { parent[i] = i; // 初始化每个节点的父节点为自己 } } public int find(int x) { if (parent[x] != x) { parent[x] = find(parent[x]); // 路径压缩 } return parent[x]; } public void union(int x, int y) { int rootX = find(x); int rootY = find(y); if (rootX == rootY) return; // 已经在同一组 parent[rootY] = rootX; } }}