【图论常见模板题】4种最短路解法和2种最小生成树解法

朴素版Dijkstra

  • O(n^2),适用于稠密图,使用邻接矩阵
  1. 每一次找出一个离源点最近的点
  2. 将这个点加入最短路集合
  3. 使用这个点去更新其他点到源点的距离

堆优化版Dijkstra

  • O(mlogm),适用于稀疏图,使用邻接表
  1. 每一次从小根堆中获得离源点最近的点
  2. 将这个点加入到最短路集合中
  3. 使用这个点去更新从这个点出发可以到达的点到源点的距离

Bellman-Ford

  • O(nm),常用于有限制次数的最短路,使用Edge结构体
  1. 将所有的点加入到队列中
  2. 更新k次,每一次都将所有的点进行更新

Spfa

  • 最好O(n),最坏O(nm),可以判断是否出现负权回路
  1. 将源点加入到队列中
  2. 使用源点去更新其他的点,并将被更新过的点放入队列中,下一轮去更新其他的点。其中为了保证相同点不同在同一轮中不被重复放入队列中,可以使用boolean[]

Kruskal

  • O(mlogm),适用于稀疏图,使用Edge结构体
  1. 将所有边按照权重排升序
  2. 从小到大选择边加入到最小生成树中,如果点所属集合不同,就将不同点加入到同一个集合中,最终形成一个集合

Prim

  • O(n^2),适用于稠密图,使用邻接矩阵
  1. 每一次找出一个离源点集合最近的点
  2. 将这个点加入最小生成树集合中
  3. 使用这个点去更新其他点到源点集合的距离

Dijkstra求最短路

给定一个 nn 个点 mm 条边的有向图,图中可能存在重边和自环,所有边权均为正值。

请你求出 11 号点到 nn 号点的最短距离,如果无法从 11 号点走到 nn 号点,则输出 −1−1。

  • 输入格式

第一行包含整数 nn 和 mm。

接下来 mm 行每行包含三个整数 x,y,zx,y,z,表示存在一条从点 xx 到点 yy 的有向边,边长为 zz。

  • 输出格式

输出一个整数,表示 11 号点到 nn 号点的最短距离。

如果路径不存在,则输出 −1−1。

  • 数据范围

1≤n≤5001≤n≤500,
1≤m≤1051≤m≤105,
图中涉及边长均不超过10000。

  • 输入样例
3 3
1 2 2
2 3 1
1 3 4
  • 输出样例
3
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Arrays;

public class Main {
    final static int INF = 0x3f3f3f3f;
    static int n, m;
    static int[][] g;
    public static void main(String[] args) throws IOException {
        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
        String[] cur = in.readLine().split(" ");
        n = Integer.parseInt(cur[0]);
        m = Integer.parseInt(cur[1]);
        g = new int[n + 1][n + 1];
        for (int i = 0; i <= n; i ++) {
            Arrays.fill(g[i], INF);
        }
        for (int i = 0; i < m; i ++) {
            String[] str = in.readLine().split(" ");
            int a = Integer.parseInt(str[0]);
            int b = Integer.parseInt(str[1]);
            int c = Integer.parseInt(str[2]);
            g[a][b] = Math.min(g[a][b], c);
        }
        Dijkstra();
    }
    public static void Dijkstra() {
        boolean[] vis = new boolean[n + 1];
        int[] dist = new int[n + 1];
        Arrays.fill(dist, INF);
        dist[1] = 0;
        for (int i = 1; i <= n; i ++) {
            int t = -1;
            for (int j = 1; j <= n; j ++) {
                if (!vis[j] && (t == -1 || dist[t] > dist[j])) {
                    t = j;
                }
            }
            vis[t] = true;
            for (int j = 1; j <= n; j ++) {
                dist[j] = Math.min(dist[j], dist[t] + g[t][j]);
            }
        }
        if (dist[n] == INF) System.out.println("-1");
        else System.out.println(dist[n]);
    }
}

堆优化版的Dijkstra求最短路

给定一个 nn 个点 mm 条边的有向图,图中可能存在重边和自环,所有边权均为非负值。

请你求出 11 号点到 nn 号点的最短距离,如果无法从 11 号点走到 nn 号点,则输出 −1−1。

  • 输入格式

第一行包含整数 nn 和 mm。

接下来 mm 行每行包含三个整数 x,y,zx,y,z,表示存在一条从点 xx 到点 yy 的有向边,边长为 zz。

  • 输出格式

输出一个整数,表示 11 号点到 nn 号点的最短距离。

如果路径不存在,则输出 −1−1。

  • 数据范围

1≤n,m≤1.5×1051≤n,m≤1.5×105,
图中涉及边长均不小于 00,且不超过 1000010000。
数据保证:如果最短路存在,则最短路的长度不超过 109109。

  • 输入样例
3 3
1 2 2
2 3 1
1 3 4
  • 输出样例
3
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.Comparator;
import java.util.PriorityQueue;

// 堆优化版Dijkstra
public class Main {
    static int n, m;
    static int N = 150010;
    // idx表示当前为第几个节点
    // he[i]表示以i节点为开头的链表的头结点是第几条边,e[idx]表示第idx条有向边指向哪一个顶点
    // ne[idx]表示第idx表有向边指向链表的节点,w[idx]表示第idx条有向边的边权重
    static int idx = 0;
    static int[] he = new int[N], e = new int[N], ne = new int[N], w = new int[N];
    static void add(int a, int b, int c) {
        e[idx] = b; w[idx] = c; ne[idx] = he[a]; he[a] = idx ++;
    }
    static int[] dist = new int[N];
    static boolean[] vis = new boolean[N];
    static int INF = 0x3f3f3f3f;
    public static void main(String[] args) throws IOException {
        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
        String[] cur = in.readLine().split(" ");
        n = Integer.parseInt(cur[0]);
        m = Integer.parseInt(cur[1]);
        for (int i = 0; i <= n; i ++) {
            he[i] = -1;
        }
        for (int i = 0; i < m; i ++) {
            String[] str = in.readLine().split(" ");
            int a = Integer.parseInt(str[0]);
            int b = Integer.parseInt(str[1]);
            int c = Integer.parseInt(str[2]);
            add(a, b, c);
        }
        Dijkstra();
    }
    public static void Dijkstra() {
        for (int i = 0; i <= n; i ++) {
            dist[i] = INF;
        }
        dist[1] = 0;
        PriorityQueue<int[]> q = new PriorityQueue<>(new Comparator<int[]>() {
            @Override
            public int compare(int[] a, int[] b) {
                return a[0] - b[0];
            }
        });
        // int[]第一个参数是顶点到1号的距离,第二个参数是顶点编号
        q.add(new int[]{0, 1});
        while (!q.isEmpty()) {
            int[] top = q.poll();
            int distance = top[0], vertex = top[1];
            if (vis[vertex]) continue;
            vis[vertex] = true;
            // 遍历以vertex顶点开头的所有的有向边,i是有向边的编号
            for (int i = he[vertex]; i != -1; i = ne[i]) {
                int j = e[i];
                if (distance + w[i] < dist[j]) {
                    dist[j] = distance + w[i];
                    q.add(new int[]{dist[j], j});
                }
            }
        }
        if (dist[n] == INF) System.out.println("-1");
        else System.out.println(dist[n]);
    }
}

Bellman-ford求最短路

给定一个 nn 个点 mm 条边的有向图,图中可能存在重边和自环, 边权可能为负数

请你求出从 11 号点到 nn 号点的最多经过 kk 条边的最短距离,如果无法从 11 号点走到 nn 号点,输出 impossible

注意:图中可能 存在负权回路

  • 输入格式

第一行包含三个整数 n,m,kn,m,k。

接下来 mm 行,每行包含三个整数 x,y,zx,y,z,表示存在一条从点 xx 到点 yy 的有向边,边长为 zz。

  • 输出格式

输出一个整数,表示从 11 号点到 nn 号点的最多经过 kk 条边的最短距离。

如果不存在满足条件的路径,则输出 impossible

  • 数据范围

1≤n,k≤5001≤n,k≤500,
1≤m≤100001≤m≤10000,
任意边长的绝对值不超过 1000010000。

  • 输入样例
3 3 1
1 2 1
2 3 1
1 3 3
  • 输出样例
3
import java.io.BufferedReader;  
import java.io.IOException;  
import java.io.InputStreamReader;  
import java.util.ArrayList;  
import java.util.List;

public class Main {
    private static final int INF = 0x3f3f3f3f;

    static class Edge {
        int a, b, c;
        public Edge(int a, int b, int c) {
            this.a = a;
            this.b = b;
            this.c = c;
        }
    }
    static int N = 510, M = 10010;
    static int n, m, k;
    static List<Edge> list = new ArrayList<>();
    static int[] dist = new int[N];
    public static void main(String[] args) throws IOException {
        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
        String[] cur = in.readLine().split(" ");
        n = Integer.parseInt(cur[0]);
        m = Integer.parseInt(cur[1]);
        k = Integer.parseInt(cur[2]);
        for (int i = 0; i < m; i ++) {
            String[] str = in.readLine().split(" ");
            int a = Integer.parseInt(str[0]);
            int b = Integer.parseInt(str[1]);
            int c = Integer.parseInt(str[2]);
            list.add(new Edge(a, b, c));
        }
        BellmanFord();
    }
    public static void BellmanFord() {
        for (int i = 0; i <= n; i ++) {
            dist[i] = INF;
        }
        dist[1] = 0;
        // 进行k次更新
        for (int i = 0; i < k; i ++) {
            // 每一次更新不能有连带效应,所以需要将上一次的dist深拷贝一份
            int[] prev = dist.clone();
            for (Edge edge : list) {
                dist[edge.b] = Math.min(dist[edge.b], prev[edge.a] + edge.c);
            }
        }
        if (dist[n] > INF / 2) System.out.println("impossible");
        else System.out.println(dist[n]);
    }
}

Spfa求最短路

给定一个 nn 个点 mm 条边的有向图,图中可能存在重边和自环, 边权可能为负数

请你求出 11 号点到 nn 号点的最短距离,如果无法从 11 号点走到 nn 号点,则输出 impossible

数据保证不存在负权回路。

  • 输入格式

第一行包含整数 nn 和 mm。

接下来 mm 行每行包含三个整数 x,y,zx,y,z,表示存在一条从点 xx 到点 yy 的有向边,边长为 zz。

  • 输出格式

输出一个整数,表示 11 号点到 nn 号点的最短距离。

如果路径不存在,则输出 impossible

  • 数据范围

1≤n,m≤1051≤n,m≤105,
图中涉及边长绝对值均不超过 1000010000。

  • 输入样例
3 3
1 2 5
2 3 -3
1 3 4
  • 输出样例
2
import java.io.BufferedReader;  
import java.io.IOException;  
import java.io.InputStreamReader;  
import java.util.ArrayList;  
import java.util.Deque;  
import java.util.LinkedList;  
import java.util.List;  
  
class Main {  
    private static final int INF = 0x3f3f3f3f;  
    static int n, m;  
    static int N = 100010;  
    static int[] he = new int[N], e = new int[N], w = new int[N], ne = new int[N];  
    static int idx = 0;  
    static int[] dist = new int[N];  
    static boolean[] vis = new boolean[N];  
    public static void add(int a, int b, int c) {  
        e[idx] = b; w[idx] = c; ne[idx] = he[a]; he[a] = idx ++;  
    }  
    public static void main(String[] args) throws IOException {  
        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));  
        String[] cur = in.readLine().split(" ");  
        n = Integer.parseInt(cur[0]);  
        m = Integer.parseInt(cur[1]);  
        for (int i = 0; i <= n; i ++) {  
            he[i] = -1;  
        }  
        for (int i = 0; i < m; i ++) {  
            String[] str = in.readLine().split(" ");  
            int a = Integer.parseInt(str[0]);  
            int b = Integer.parseInt(str[1]);  
            int c = Integer.parseInt(str[2]);  
            add(a, b, c);  
        }  
        Spfa();  
    }  
  
    public static void Spfa() {  
        for (int i = 0; i <= n; i ++) {  
            dist[i] = INF;  
        }  
        dist[1] = 0;  
        vis[1] = true;  
        Deque<Integer> q = new LinkedList<>();  
        q.add(1);  
        // 使用vis数组是为了防止当前已经加入队列的节点,无需重复被加入到队列中  
        // 但是因为一个顶点可能被用于多次更新路径,所以每一次使用一个顶点更新完路径之后  
        // 还需要将vis[ver]=false,使得下一次该顶点可以被更新  
        while (!q.isEmpty()) {  
            int ver = q.pollFirst();  
            vis[ver] = false;  
            for (int i = he[ver]; i != -1; i = ne[i]) {  
                int j = e[i];  
                if (w[i] + dist[ver] < dist[j]) {  
                    dist[j] = w[i] + dist[ver];  
                    if (!vis[j]) {  
                        vis[j] = true;  
                        q.add(j);  
                    }  
                }  
            }  
        }  
        if (dist[n] > INF / 2) System.out.println("impossible");  
        else System.out.println(dist[n]);  
    }  
}

Dijkstra为什不能计算带有负权值的边的图?

  • Dijkstra是基于贪心策略的,每一次都选取离当前点最近的点,使用这个点来更新其他点。但是如果图中的边有负数的话,那么当前最近的点可能就不是最好的选择,而非最近点可能因为负权边儿时的路径和不断减小。
  • ![[Pasted image 20220616104956.png]]

Dijkstra和Spfa写法上的区别?

  • Dijkstra和Spfa写法上虽然很像,但是含义是完全不同的。无论是朴素版的Dijkstra还是堆优化版的Dijkstra,每一次都是先获取离源点最近并且没有被更新过的点,然后用这个点去更新其他的路径。但是Spfa每一次只是将上一次被更新过的点,放入队列(也可以是其他的数据结构),用这些被更新过的点去更新其他的点。
  • 相对而言,基于贪心的Dijkstra更强调局部最优解,而Spfa只在意最终的最短路,所以Spfa算法的整体结构更加松弛。也是因为这样所以Spfa能够处理负权边的图。

Spafa判断负权回路

给定一个 nn 个点 mm 条边的有向图,图中可能存在重边和自环, 边权可能为负数

请你判断图中是否存在负权回路。

  • 输入格式

第一行包含整数 nn 和 mm。

接下来 mm 行每行包含三个整数 x,y,zx,y,z,表示存在一条从点 xx 到点 yy 的有向边,边长为 zz。

  • 输出格式

如果图中存在负权回路,则输出 Yes,否则输出 No

  • 数据范围

1≤n≤20001≤n≤2000,
1≤m≤100001≤m≤10000,
图中涉及边长绝对值均不超过 1000010000。

  • 输入样例
3 3
1 2 -1
2 3 4
3 1 -4
  • 输出样例
Yes
  • 因为m很大,说明边数很多,因此使用邻接表比较好。
import java.io.BufferedReader;  
import java.io.IOException;  
import java.io.InputStreamReader;  
import java.util.Deque;  
import java.util.LinkedList;  

class Main {  
    private static final int INF = 0x3f3f3f3f;  
    static int n, m;  
    static int N = 100010;  
    static int[] he = new int[N], e = new int[N], w = new int[N], ne = new int[N];  
    static int idx = 0;  
    static int[] dist = new int[N];  
    static boolean[] vis = new boolean[N];  
    // cnt[i]表示i顶点到原点的最短路径的边数  
    // 如果cnt[i]>=n的话,说明i顶点到原点的最短距离的边数超过n,那么至少有一个点被使用了n+1次  
    // 即出现了回路,而正数不可能出现回路,说明图中出现了负权回路  
    static int[] cnt = new int[N];  
    public static void add(int a, int b, int c) {  
        e[idx] = b; w[idx] = c; ne[idx] = he[a]; he[a] = idx ++;  
    }  
    public static void main(String[] args) throws IOException {  
        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));  
        String[] cur = in.readLine().split(" ");  
        n = Integer.parseInt(cur[0]);  
        m = Integer.parseInt(cur[1]);  
        for (int i = 0; i <= n; i ++) {  
            he[i] = -1;  
        }  
        for (int i = 0; i < m; i ++) {  
            String[] str = in.readLine().split(" ");  
            int a = Integer.parseInt(str[0]);  
            int b = Integer.parseInt(str[1]);  
            int c = Integer.parseInt(str[2]);  
            add(a, b, c);  
        }  
        if(Spfa()) System.out.println("Yes");  
        else System.out.println("No");  
    }  
    public static boolean Spfa() {  
		// dist数组初不初始化都可以,因为如果出现负权回路的话,dist[i]是多少都会被更新
		for (int i = 0; i <= n; i ++) {
			dist[i] = INF;
		}
        Deque<Integer> q = new LinkedList<>();  
		// 为了避免一个点不能达到负权回路,所以需要将所有的路径都考虑在内,因此需要将所有顶点都放入队列中进行检验
        for (int i = 1; i <= n; i ++) {  
            vis[i] = true;  
            q.add(i);  
        }  
        while (!q.isEmpty()) {  
            int ver = q.pollFirst();  
            vis[ver] = false;  
            for (int i = he[ver]; i != -1; i = ne[i]) {  
                int j = e[i];  
                if (dist[j] > dist[ver] + w[i]) {  
                    dist[j] = dist[ver] + w[i];  
                    // 每当j顶点被更新,说明从ver点到j点上的最短路径多了一条边  
                    cnt[j] = cnt[ver] + 1;  
                    if (cnt[j] > n) return true;  
                    if (!vis[j]) {  
                        vis[j] = true;  
                        q.add(j);  
                    }  
                }  
            }  
        }  
        return false;  
    }  
}
  • 注意:这面这种写法只是为了检验路径中是否存在回路,所以并没有将标准的Spfa计算最短路写出来。

Floyd求最短路

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

再给定 kk 个询问,每个询问包含两个整数 xx 和 yy,表示查询从点 xx 到点 yy 的最短距离,如果路径不存在,则输出 impossible

数据保证图中不存在负权回路。

  • 输入格式

第一行包含三个整数 n,m,kn,m,k。

接下来 mm 行,每行包含三个整数 x,y,zx,y,z,表示存在一条从点 xx 到点 yy 的有向边,边长为 zz。

接下来 kk 行,每行包含两个整数 x,yx,y,表示询问点 xx 到点 yy 的最短距离。

  • 输出格式

共 kk 行,每行输出一个整数,表示询问的结果,若询问两点间不存在路径,则输出 impossible

  • 数据范围

1≤n≤2001≤n≤200,
1≤k≤n21≤k≤n2
1≤m≤200001≤m≤20000,
图中涉及边长绝对值均不超过 1000010000。

  • 输入样例
3 3 2
1 2 1
2 3 2
1 3 1
2 1
1 3
  • 输出样例
impossible
1

[[动态规划]]

  • dp[k][i][j]表示最短路考虑了k节点作为中间经过节点。
  • 递推公式:dp[k][i][j] = Math.min(dp[k-1][i][j], dp[k-1][i][k]+dp[k-1][k][j])
    • dp[k-1][i][j]表示从i到j不经过k节点的最短路径
    • dp[k-1][i][k]+dp[k-1][k][j]表示从i到k再到j的最短路径。
  • 由于递归公式中dp[k]只和dp[k-1]有关,并且经过计算的dp[k][i][j]会变得更小,不影响最终结果,因此可以省掉一维空间限制。dp[i][j]=Math.min(dp[i][j], dp[i][k]+dp[k][j]),但是循环的顺序不能改变,还是要先循环中间节点变量k
import java.io.BufferedReader;  
import java.io.IOException;  
import java.io.InputStreamReader;  

class Main {  
    static int N = 210;  
    static int[][] g = new int[N][N];  
    static int n, m, k;  
    static int INF = 0x3f3f3f3f;  
    public static void main(String[] args) throws IOException {  
        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));  
        String[] cur = in.readLine().split(" ");  
        n = Integer.parseInt(cur[0]);  
        m = Integer.parseInt(cur[1]);  
        k = Integer.parseInt(cur[2]);  
        // 注意要是用floyd,一定要保证顶点到自己距离为0  
        for (int i = 0; i <= n; i ++) {  
            for (int j = 0; j <= n; j ++) {  
                if (i == j) g[i][j] = 0;  
                else g[i][j] = INF;  
            }  
        }  
        for (int i = 0; i < m; i ++) {  
            String[] str = in.readLine().split(" ");  
            int a = Integer.parseInt(str[0]);  
            int b = Integer.parseInt(str[1]);  
            int c = Integer.parseInt(str[2]);  
            g[a][b] = Math.min(g[a][b], c);  
        }  
        Floyd();  
        while (k -- > 0) {  
            String[] str = in.readLine().split(" ");  
            int a = Integer.parseInt(str[0]);  
            int b = Integer.parseInt(str[1]);  
            if (g[a][b] == INF) System.out.println("impossible");  
            else System.out.println(g[a][b]);  
        }  
    }  
    public static void Floyd() {  
        for (int k = 1; k <= n; k ++) {  
            for (int i = 1; i <= n; i++) {  
                for (int j = 1; j <= n; j++) {  
                    g[i][j] = Math.min(g[i][j], g[i][k] + g[k][j]);  
                }  
            }  
        }  
    }  
}

Kruskal求最小生成树

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

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

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

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

  • 输入格式

第一行包含两个整数 nn 和 mm。

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

  • 输出格式

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

  • 数据范围

1≤n≤1051≤n≤105,
1≤m≤2∗1051≤m≤2∗105,
图中涉及边的边权的绝对值均不超过 10001000。

  • 输入样例
4 5
1 2 1
1 3 2
1 4 3
2 3 2
3 4 4
  • 输出样例
6

[[并查集]]+[[贪心]]

  • 将边的权重进行排序,优先选择最小权重的边作为最小生成树的分支。
  • 被选入成为最小生成树的边需要被标记已经被选入了,但是不能使用boolean[]进行标记。因为可能出现一个边已经被选择了,但是这条边只是最小生成树的一棵子树,之后这棵子树还要被加入到最小生成树的另一部分中。如果使用boolean标记的话,一提边被标记之后就不能在被选择了,此时就需要使用并查集,将不同的定点所属的集合能够区分开来。
import java.io.BufferedReader;  
import java.io.IOException;  
import java.io.InputStreamReader;  
import java.util.ArrayList;  
import java.util.Collections;  
import java.util.Comparator;  
import java.util.List;

class Main {  
    static int n, m;  
    static List<Edge> list = new ArrayList<>();  
    static int N = 100010, M = 200010;  
    static boolean[] vis = new boolean[N];  
    static int[] p = new int[N];  
    static class Edge {  
        int a, b, c;  
        public Edge(int a, int b, int c) {  
            this.a = a;  
            this.b = b;  
            this.c = c;  
        }  
    }  
    public static void main(String[] args) throws IOException {  
        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));  
        String[] cur = in.readLine().split(" ");  
        n = Integer.parseInt(cur[0]);  
        m = Integer.parseInt(cur[1]);  
        for (int i = 0; i < m; i ++) {  
            String[] str = in.readLine().split(" ");  
            int a = Integer.parseInt(str[0]);  
            int b = Integer.parseInt(str[1]);  
            int c = Integer.parseInt(str[2]);  
            list.add(new Edge(a, b, c));  
        }  
        kruskal();  
    }  
  
    public static void kruskal() {  
        // 初始化并查集  
        for (int i = 0; i <= n; i ++) {  
            p[i] = i;  
        }  
        Collections.sort(list, new Comparator<Edge>(){  
            public int compare(Edge a, Edge b) {  
                return a.c - b.c;  
            }  
        }); 
		// cnt表示当前最小生成树中边的条数
		// 虽然判断最小生成树有两种方式,一种使用边的条数,一种使用点的个数。由于每一次加入集合中顶点的个数可能是1个或者2个,所以使用统计的边的方式来进行判定
        int cnt = 0;  
        int ans = 0;  
        for (Edge edge : list) {  
            int a = edge.a, b = edge.b;  
            // 找到顶点自己所属的集合,如果顶点所属集合不同,就将顶点加入到同一个集合中  
            a = find(a);  
            b = find(b);  
            if (a != b) {  
                p[a] = b;  
                cnt ++;  
                ans += edge.c;  
            }  
        }  
        if (cnt == n - 1) System.out.println(ans);  
        else System.out.println("impossible");  
    }  
  
    public static int find(int x) {  
        if (x != p[x]) p[x] = find(p[x]);  
        return p[x];  
    }
}

Prim求最小生成树

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

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

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

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

  • 输入格式

第一行包含两个整数 nn 和 mm。

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

  • 输出格式

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

  • 数据范围

1≤n≤5001≤n≤500,
1≤m≤1051≤m≤105,
图中涉及边的边权的绝对值均不超过 1000010000。

  • 输入样例
4 5
1 2 1
1 3 2
1 4 3
2 3 2
3 4 4
  • 输出样例
6

[[贪心]]

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

class Main {  
    static int n, m;  
    static int N = 510;  
    static int[][] g = new int[N][N];  
    static int INF = 0x3f3f3f3f;  
    static int[] dist = new int[N];  
    static boolean[] vis = new boolean[N];  
    public static void main(String[] args) throws IOException {  
        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));  
        String[] cur = in.readLine().split(" ");  
        n = Integer.parseInt(cur[0]);  
        m = Integer.parseInt(cur[1]);  
        for (int i = 0; i <= n; i ++) {  
            for (int j = 0; j <= n; j ++) {  
                if (i == j) g[i][j] = 0;  
                else g[i][j] = INF;  
            }  
        }  
        for (int i = 0; i < m; i ++) {  
            String[] str = in.readLine().split(" ");  
            int a = Integer.parseInt(str[0]);  
            int b = Integer.parseInt(str[1]);  
            int c = Integer.parseInt(str[2]);  
            g[a][b] = g[b][a] = Math.min(g[a][b], c);  
        }  
        int ans = prim();  
        if (ans == INF) System.out.println("impossible");  
        else System.out.println(ans);  
    }  
    public static int prim() {  
        // dist表示的是顶点到最小生成树组成的集合的距离  
        for (int i = 0; i <= n; i ++) {  
            dist[i] = INF;  
        }  
        dist[1] = 0;  
        int ans = 0;  
        for (int i = 1; i <= n; i ++) {  
            int t = -1;  
            for (int j = 1; j <= n; j ++) {  
                if (!vis[j] && (t == -1 || dist[t] > dist[j])) {  
                    t = j;  
                }  
            }  
            if (dist[t] == INF) return INF;  
            vis[t] = true;  
            ans += dist[t];  
            for (int j = 1; j <= n; j ++) {  
                // 使用离连通部分最近的点去更新其他的点  
                if (!vis[j] && dist[j] > g[t][j]) {  
                    dist[j] = g[t][j];  
                }  
            }  
        }  
        return ans;  
    }  
}

Prim和Dijkstra的比较

  • 从写法上看Prim和朴素版Dijkstra除了在最后使用点更新其他边不一样,其余的部分几乎一模一样。但是Prim和Dijkstra含义上最大的不同是Prim每一次选出一个离源点集合(即最小生成树形成的集合)最近的点。所以每一次更新的时候,dist[j]需要使用g[t][j]来更新。因为此时t在源点集合中,即t表示的就是源点集合。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hyzhang_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值