这是我参与「第四届青训营 」笔记创作活动的第8天\
概述
最小生成树问题研究的是无向图,主要为 Prim 和 Kruskal 两个算法。这两个算法都是贪心算法,符合全局最优包含局部最优的“最优性原理”。其中Prim以点的是否纳入有别于Kruskal的判断最短边是否纳入。
- Prim演示:
- Kruskal演示:
因Kruskal的代码更易理解,故稍后的代码先上Kruskal。
切入口(例题)
L 城一共有 N 个小区。
小明是城市建设的规划者,他计划在城市修 M 条路,每修建一条路都要支付工人们相应的工钱(需要支付的工钱 = 路的长度)。
然而小明所拿到的经费并不够支付修建 M 条路的工钱,于是迫于无奈,他只能将计划改变为修建若干条路,使得 N 个小区之间两两联通。
小明希望尽量剩下更多的经费投入到别的项目中,因此请你通过程序帮他计算出完成计划所需的最低开销。
样例:
5 6
1 2 2
1 3 7
1 4 6
2 3 1
3 4 3
3 5 2
输出:8\
Kruskal(JAVA)
import java.util.*;
import java.io.*;
class edge{
int from,to,dis;
public edge(int a,int b,int c){
from=a; to=b; dis=c;
}
}
public class Main{
static StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
static int read()throws IOException{
in.nextToken();
return(int)in.nval;
}
static int N = (int)2e6 + 10;
static edge e[] = new edge[N];
static int n,m,s[]=new int[N];
static int find(int x){//查询并查集,返回x的根
return s[x]==x?x:(s[x]=find(s[x])) ;
}
static long kruskal(){
long ans = 0;
for(int i = 1;i <= n;i ++) s[i] = i;//并查集初始化
Arrays.sort(e,1,m+1,new Comparator<edge>(){ //对边做排序
public int compare(edge a,edge b){return a.dis - b.dis;}
});
for(int i = 1;i <= m;i ++){ //贪心:逐一加入每个边
int x = find(e[i].from),y = find(e[i].to);
if(x == y) continue;
n--;
ans += e[i].dis;
s[x] = y; //合并
}
return ans;
}
public static void main(String args[])throws IOException{
n = read();
m = read();
for(int i = 1;i <= m;i++){
int x = read();
int y = read();
int z = read();
e[i] = new edge(x,y,z);
}
long ans = kruskal();
System.out.print(n==1?ans:"-1");
}
}
Prim(JAVA)
import java.io.*;
import java.util.*;
public class Main {
static int N,M;
static List<int[]>[] edges=new ArrayList[100001];
static PriorityQueue<int[]> pq=new PriorityQueue<>(new Comparator<int[]>() {
public int compare(int[] o1, int[] o2) {return o1[1]-o2[1]; }
});
static int[] dis;
static int[] vis;
static long ans;
static int MAX=Integer.MAX_VALUE;
static BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
static StreamTokenizer st=new StreamTokenizer(br);
static int nextInt() throws IOException {
st.nextToken();
return (int)st.nval;
}
public static void main(String[] args) throws IOException {
N=nextInt();
M=nextInt();
dis=new int[N+1];
vis=new int[N+1];
for (int j = 1; j <= N; j++) {
if(edges[j]==null) {
edges[j]=new ArrayList<>();
}else {
edges[j].clear();
}
}
int a,b,c=0;
for (int j = 1; j <= M; j++) {
a=nextInt();
b=nextInt();
c=nextInt();
edges[a].add(new int[] {b,c});
edges[b].add(new int[] {a,c});
}
Arrays.fill(dis, MAX);
prim();
System.out.println(ans);
Arrays.fill(vis,0);
pq.clear();
}
private static void prim() {
int count = 0;
ans = 0;
pq.add(new int[] {1,0});
dis[1]=0;
while(!pq.isEmpty()) {
int[] point=pq.poll();
if (vis[point[0]] == 1) continue;
vis[point[0]] = 1;
if (point[1] != 0) {
ans += point[1];
count++;
}
for (int i = 0; i < edges[point[0]].size(); i++) {
int[] next=edges[point[0]].get(i);
if(next[1]<dis[next[0]]) {
dis[next[0]]=next[1];
pq.add(new int[] {next[0],dis[next[0]]});
}
}
}
if (count != N - 1) ans = -1;
}
}
回顾
Prim------点
Kruskal---边
故Prim更适合稠密图,Kruskal在边很多的的时候不如Prim。
参考资料: 蓝桥云课