最小生成树

最小生成树

最小生成树是所有节点的最小连通子图, 即:以最小的成本(边的权值)将图中所有节点链接到一起。

alt text

图中有n个节点,那么一定可以用 n - 1 条边将所有节点连接到一起。 那么如何选择 这 n-1 条边? 这就是最小生成树算法的任务所在。

prim 算法

重要

prim 算法步骤:

  1. 选距离生成树最近节点

  2. 最近节点加入生成树

  3. 更新非生成树节点到生成树的距离(即更新minDist数组)

minDist数组用来记录每一个节点距离最小生成树的最近距离minDist数组里记录的其实也是 最小生成树的边的权值

 1import java.util.*;
 2
 3public class Main {
 4    public static void main(String[] args) {
 5        Scanner scanner = new Scanner(System.in);
 6        int v = scanner.nextInt();
 7        int e = scanner.nextInt();
 8
 9        // 初始化邻接矩阵,所有值初始化为一个大值,表示无穷大
10        int[][] grid = new int[v + 1][v + 1];
11        for (int i = 0; i <= v; i++) {
12            Arrays.fill(grid[i], 10001);
13        }
14
15        // 读取边的信息并填充邻接矩阵
16        for (int i = 0; i < e; i++) {
17            int x = scanner.nextInt();
18            int y = scanner.nextInt();
19            int k = scanner.nextInt();
20            grid[x][y] = k;
21            grid[y][x] = k;
22        }
23
24        // 所有节点到最小生成树的最小距离
25        int[] minDist = new int[v + 1];
26        Arrays.fill(minDist, 10001);
27
28        // 记录节点是否在树里
29        boolean[] isInTree = new boolean[v + 1];
30
31        // Prim算法主循环
32        for (int i = 1; i < v; i++) {
33            int cur = -1;
34            int minVal = Integer.MAX_VALUE;
35
36            // 选择距离生成树最近的节点
37            for (int j = 1; j <= v; j++) {
38                if (!isInTree[j] && minDist[j] < minVal) {
39                    minVal = minDist[j];
40                    cur = j;
41                }
42            }
43
44            // 将最近的节点加入生成树
45            isInTree[cur] = true;
46
47            // 更新非生成树节点到生成树的距离
48            for (int j = 1; j <= v; j++) {
49                if (!isInTree[j] && grid[cur][j] < minDist[j]) {
50                    minDist[j] = grid[cur][j];
51                }
52            }
53        }
54
55        // 统计结果
56        int result = 0;
57        for (int i = 2; i <= v; i++) {
58            result += minDist[i];
59        }
60        System.out.println(result);
61        scanner.close();
62    }
63}

kruskal 算法

重要

kruscal 的思路

  1. 边的权值排序,因为要优先选最小的边加入到生成树里

  2. 遍历排序后的边:

    • 如果边首尾的两个节点在同一个集合,说明如果连上这条边图中会出现环

    • 如果边首尾的两个节点不在同一个集合,加入到最小生成树,并把两个节点加入同一个集合

import java.util.*;

class Edge {
    int l, r, val;

    Edge(int l, int r, int val) {
        this.l = l;
        this.r = r;
        this.val = val;
    }
}

public class Main {
    private static int n = 10001;
    private static int[] father = new int[n];

    // 并查集初始化
    public static void init() {
        for (int i = 0; i < n; i++) {
            father[i] = i;
        }
    }

    // 并查集的查找操作
    public static int find(int u) {
        if (u == father[u]) return u;
        return father[u] = find(father[u]);
    }

    // 并查集的加入集合
    public static void join(int u, int v) {
        u = find(u);
        v = find(v);
        if (u == v) return;
        father[v] = u;
    }

    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));
        }

        // 执行Kruskal算法
        edges.sort(Comparator.comparingInt(edge -> edge.val));

        // 并查集初始化
        init();

        // 从头开始遍历边
        for (Edge edge : edges) {
            int x = find(edge.l);
            int y = find(edge.r);

            if (x != y) {
                result_val += edge.val;
                join(x, y);
            }
        }
        System.out.println(result_val);
        scanner.close();
    }
}

参考

  1. prim算法精讲. 代码随想录

  2. kruskal算法精讲. 代码随想录