传送门:P3366 【模板】最小生成树 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)https://www.luogu.com.cn/problem/P3366
这道题有两种常规做法,kruskal(对边进行研究) 和 prim(对节点进行研究,类似dijikstra)。本文主要讲解kruskal算法。
kruskal算法的第一步是给所有边按照从小到大的顺序排序。这一步可以直接使用Colleciton.sort(list, 匿名内部类)。
Collections.sort(list, new Comparator<node>() {
@Override
public int compare(node o1, node o2) {
return o1.w-o2.w;
}
});
class node{
int u,v,w;
}
当然,可以用下面这行代码简化,一样的道理:
Collections.sort(list, Comparator.comparingInt(o -> o.w));
接下来从小到大一次考察每条边(u,v)。
情况一:u和v在同一个连通分量中(即u和v是连通的),那么加入(u,v)后会形成环,因此不能选择。
情况二:如果u和v在不同的连通分量,那么加入(u,v)一定是最优的。为什么呢?证明: 如果某个连通图属于最小生成树,那么所有从外部连接到该连通图的边中的一条最短的边必然属于最小生成树。所以不难发现,当最小生成树被拆分成彼此独立的若干个连通分量的时候,所有能够连接任意两个连通分量的边中的一条最短边必然属于最小生成树。
现在问题的关键就是如何判断两个连通分量(两个子图)是否是连通的。这里使用并查集是一个很好的选择。如果两个节点不属于同一个集合,那么这条边就是我们要找的边,将这两个节点合并成同一个节点。
给出详细代码:
import java.io.*;
import java.util.*;
class node { //用node来表示节点
int u,v,w;
public node(int u,int v,int w){
this.u=u;
this.v=v;
this.w=w;
}
}
public class Main {
static int N=5001, M=200001; //记录n,m最大值
static int n,m,cnt,ans; //个人习惯,将变量定义成全局
static int[] fa = new int[N]; //并查集用到的数组
static List<node> list = new ArrayList<>(); //存放节点
public static void main(String[] args) throws IOException {
n=nextInt();
m=nextInt();
for(int i=1;i<=n;i++) fa[i] = i; //初始化数组
for(int i=0;i<m;i++)
list.add(new node(nextInt(),nextInt(),nextInt()));
kruskal();
if(cnt == n-1) out.println(ans); //注意是n-1,n个点只需要n-1条边即可互通
else out.println("orz");
out.close(); //最后别忘了刷新流,不然无结果输出
}
public static void kruskal() {
Collections.sort(list, Comparator.comparingInt(o -> o.w)); //从小到大排序
for(int i=0;i<m;i++){
int u=list.get(i).u;
int v=list.get(i).v;
int w=list.get(i).w;
int fu = find(u),fv = find(v); //查找u和v的祖先
if(fu == fv) continue; //如果祖先相同,说明这两个节点是互通的
fa[fu] = v; //使两个节点互通,或者说合并两个集合
ans += w; //更新答案
if(++cnt == n-1) break; //当所有节点连通的时候,break
}
}
public static int find(int x) {
if(x == fa[x]) return x; //如果x点就是集合的祖先,直接返回
fa[x] = find(fa[x]); //查找点x的祖先
return fa[x];
}
//魔法快读,不懂的小伙伴可以看一看http://t.csdn.cn/KAQoI
static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
static StreamTokenizer in = new StreamTokenizer(br);
static PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.out)));
public static int nextInt() throws IOException{
in.nextToken();
return (int)in.nval;
}
public static String nextString() throws IOException {
in.nextToken();
return in.sval;
}
}