最小生成树


最小生成树是一副连通加权无向图中一颗权值最小的是生成树。

在给定的无向图G = (V,E)中,(u,v)代表连接顶点u与顶点v的边(即(u,v) ∈ E),而w(u,v)代表此边的权重,若存在T为E的子集(即T⊆E)且(V,T)为树,使得w(T) = ∑ w(u,v)(u,v) ∈ T的w(T)最小,则此T为G的最小生成树

最小生成树其实是最小权重生成树的简称 。


例题:


给定一个 n 个点 m 条边的无向图,图中可能存在重边和自环,边权可能为负数。

求最小生成树的树边权重之和,如果最小生成树不存在则输出 impossible

给定一张边带权的无向图 G=(V,E),其中 V 表示图中点的集合,E 表示图中边的集合,n=|V|,m=|E|。

由 V 中的全部 n 个顶点和 E 中 n−1 条边构成的无向连通子图被称为 G 的一棵生成树,其中边的权值之和最小的生成树被称为无向图 G 的最小生成树。

输入格式

第一行包含两个整数 n 和 m。

接下来 m 行,每行包含三个整数 u,v,w,表示点 u 和点 v 之间存在一条权值为 w 的边。

输出格式

共一行,若存在最小生成树,则输出一个整数,表示最小生成树的树边权重之和,如果最小生成树不存在则输出 impossible

数据范围

1≤n≤500
1≤m≤10^5^
图中涉及边的边权的绝对值均不超过 10000。

输入样例:

4 5
1 2 1
1 3 2
1 4 3
2 3 2
3 4 4

输出样例:

6

Prim算法


Prim算法的每一步都会为一棵生长中的树添加一条边,该树最开始只有一个顶点,然后会添加V - 1个边。每次总是添加生长中的树和树中除该生长的树以外的部分形成的切分的具有最小权值的横切边。

Prim算法的时间复杂度为O(E + Vlog V)

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;

public class Main {
    static int N = 510;
    static int g[][] = new int[N][N];
    static boolean st[] = new boolean[N];
    static int dist[] = new int[N];
    static int INF = 0x3f3f3f3f;
    static int n,m;

    public static void main(String[] args) throws IOException {
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
        String[] s = bufferedReader.readLine().split(" ");
        n = Integer.parseInt(s[0]);
        m = Integer.parseInt(s[1]);
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= n; j++) {
                if (i == j) g[i][j] = 0;
                else g[i][j] = INF;
            }
        }

        while (m -- > 0){
            String[] s1 = bufferedReader.readLine().split(" ");
            int a = Integer.parseInt(s1[0]);
            int b = Integer.parseInt(s1[1]);
            int c = Integer.parseInt(s1[2]);
            g[a][b] = Math.min(g[a][b],c);
            g[b][a] = Math.min(g[b][a],c);
        }
        int t = prim();
        if (t == INF) System.out.println("impossible");
        else System.out.println(t);
        
        bufferedReader.close();
    }

    private static int prim(){
        Arrays.fill(dist,INF);
        int res = 0;
        for (int i = 0; i < n; i++) {
            int t = -1;
            for (int j = 1; j <= n; j++) {
                if (!st[j] && (t == -1 || dist[t] > dist[j])){
                    t = j;
                }
            }
            if (i != 0 && dist[t] == INF) return INF;
            st[t] = true;
            if (i != 0) res += dist[t];
            for (int j = 1; j <= n; j++) {
                dist[j] = Math.min(dist[j],g[t][j]);
            }
        }
        return res;
    }
}

Kruskal算法


按照边的权重顺序(从小到大)将边加入生成树中,但是若加入该边会与生成树形成环则不加入该边。直到树中含有V - 1条边为止。这些边组成的就是该图的最小生成树。

Kruskal算法的时间复杂度为ElogE

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;

public class Main {
    static int N = (int)1e5 + 10;
    static int INF = 0x3f3f3f3f;
    static int p[] = new int[N];
    static PII f[] = new PII[N];
    static int res;
    static int n,m;

    public static void main(String[] args) throws IOException {
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
        String[] s = bufferedReader.readLine().split(" ");
        n = Integer.parseInt(s[0]);
        m = Integer.parseInt(s[1]);

        for (int i = 0; i < m; i++) {
            String[] s1 = bufferedReader.readLine().split(" ");
            int a = Integer.parseInt(s1[0]);
            int b = Integer.parseInt(s1[1]);
            int c = Integer.parseInt(s1[2]);
            f[i] = new PII(a,b,c);
        }

        int t = Kruskal();
        if (t == INF) System.out.println("impossible");
        else System.out.println(t);

        bufferedReader.close();
    }

    private static int find(int x){
        if (x != p[x]) p[x] = find(p[x]);
        return p[x];
    }

    private static int Kruskal(){
        Arrays.sort(f,0,m);
        for (int i = 0; i < n; i++) {
            p[i] = i;
        }
        int cnt = 0;

        for (int i = 0; i < m; i++) {
            PII pii = f[i];
            int a = pii.a;
            int b = pii.b;
            int w = pii.w;

            int pa = find(a);
            int pb = find(b);

            if (pa != pb){
                p[pa] = pb;
                cnt ++;
                res += w;
            }
        }
        if (cnt < n - 1) return INF;
        else return res;
    }
}

class PII implements Comparable<PII>{
    int a,b,w;

    public PII(int a, int b, int w) {
        this.a = a;
        this.b = b;
        this.w = w;
    }

    @Override
    public int compareTo(PII o) {
        return Integer.compare(w,o.w);
    }
}