P3366 【模板】最小生成树 java Kruskal算法 洛谷

本文详细介绍了Kruskal算法在构建最小生成树中的应用,包括算法步骤、边的排序和并查集的使用。通过排序边并检查边是否形成环,结合并查集判断节点连通性,最终找到最小生成树。代码示例展示了如何实现这一过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

        传送门:P3366 【模板】最小生成树 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)icon-default.png?t=M85Bhttps://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;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

玛卡左家陇分卡

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

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

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

打赏作者

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

抵扣说明:

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

余额充值