文章目录
朴素版Dijkstra
- O(n^2),适用于稠密图,使用邻接矩阵
- 每一次找出一个离源点最近的点
- 将这个点加入最短路集合中
- 使用这个点去更新其他点到源点的距离
堆优化版Dijkstra
- O(mlogm),适用于稀疏图,使用邻接表
- 每一次从小根堆中获得离源点最近的点
- 将这个点加入到最短路集合中
- 使用这个点去更新从这个点出发可以到达的点到源点的距离
Bellman-Ford
- O(nm),常用于有限制次数的最短路,使用Edge结构体
- 将所有的点加入到队列中
- 更新k次,每一次都将所有的点进行更新
Spfa
- 最好O(n),最坏O(nm),可以判断是否出现负权回路
- 将源点加入到队列中
- 使用源点去更新其他的点,并将被更新过的点放入队列中,下一轮去更新其他的点。其中为了保证相同点不同在同一轮中不被重复放入队列中,可以使用
boolean[]
Kruskal
- O(mlogm),适用于稀疏图,使用Edge结构体
- 将所有边按照权重排升序
- 从小到大选择边加入到最小生成树中,如果点所属集合不同,就将不同点加入到同一个集合中,最终形成一个集合
Prim
- O(n^2),适用于稠密图,使用邻接矩阵
- 每一次找出一个离源点集合最近的点
- 将这个点加入最小生成树集合中
- 使用这个点去更新其他点到源点集合的距离
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表示的就是源点集合。