求最小生成树模板

本文详细介绍了两种经典图论算法——Prim算法和Kruskal算法,用于寻找图的最小生成树。Prim算法适用于稠密图,时间复杂度为O(n^2),而Kruskal算法则更适合稀疏图,时间复杂度为O(m log n)。Prim算法通过不断添加距离树最近的节点来构建最小生成树,Kruskal算法则利用贪心策略和并查集来寻找最小权重的边。这两种算法各有优劣,具体使用需根据图的特性进行选择。
摘要由CSDN通过智能技术生成

Prim
题目链接:https://www.acwing.com/problem/content/description/860/

时间复杂度:O( n 2 n^2 n2)

适用于:稠密图,如果用堆优化也可用于稀疏图,但是稀疏图用Kruskal算法更加实用

算法思想:
d i j k s t r a dijkstra dijkstra 类似,循环 n n n 次,每次找到一个距离树最近的点,把该点加入树中,并用该点去更新其他点到树的距离

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 510,INF = 0x3f3f3f3f;

int n,m;
int g[N][N],dist[N]; // 存储各个节点到生成树的距离
bool vis[N]; // 节点是否被加入到生成树中

int prim()
{
    memset(dist,0x3f,sizeof dist);
    int res = 0;
    for(int i = 0; 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;
        if(i && dist[t] == INF) return INF; // 不联通
        if(i) res += dist[t];
        for(int j = 1; j <= n; j++) dist[j] = min(dist[j],g[t][j]); // 更新生成树外的点到生成树的距离
    }
    return res;
}


int main()
{
    scanf("%d%d",&n,&m);
    memset(g,0x3f,sizeof g);
    while(m--)
    {
        int a,b,w;
        scanf("%d%d%d",&a,&b,&w);
        g[a][b] = g[b][a] = min(g[a][b],w);
    }
    int t = prim();
    if(t == INF) printf("impossible\n");
    else printf("%d\n",t);
    return 0;
}

Kruskal
题目链接:https://www.acwing.com/problem/content/description/861/

时间复杂度:O( m l o g n mlogn mlogn)

适用于:稀疏图

算法思想:
利用贪心+并查集,先初始化并查集,将权值从小到大排序,循环 m m m 次,每次看当前边的两个点是否在树上,如果不在就加入,最后树上只要有 n − 1 n-1 n1 条边就说明存在最小生成树

#include <iostream>
#include <cstdio>
#include <algorithm>

using namespace std;

const int N = 200010;

struct Edge{
    int a;
    int b;
    int w;
    
    bool operator < (const Edge &t)const
    {
        return w < t.w; // 在结构体里重载运算符 w < t.w 降序 w > t.w 升序
    }
}edges[N];

int n,m;
int p[N];

int find(int x)
{
    if(p[x] != x) p[x] = find(p[x]);
    return p[x];
}


int main()
{
    scanf("%d%d",&n,&m);
    for(int i = 0; i < m; i++)
    {
        int a,b,w;
        scanf("%d%d%d",&a,&b,&w);
        edges[i] = {a,b,w};
    }
    sort(edges,edges+m); // 将边的权重按照大小排序
    int cnt = 0,res = 0; // res记录最小生成树的树边权重之和,cnt记录的是全部加入到树的集合中边的数量(可能有多个集合)
    
    for(int i = 1; i <= n; i++) p[i] = i; // 初始化并查集
    
    for(int i = 0; i < m; i++)
    {
        int a,b,w;
        a = find(edges[i].a);
        b = find(edges[i].b);
        w = edges[i].w;
        if(a != b)
        {
            cnt++;
            res += w;
            p[a] = b;
        }
    }
    
    
    if(cnt < n-1) printf("impossible\n"); // 树中有n个节点便有n-1条边,如果cnt不等于n-1的话,说明无法生成有n个节点的树
    else printf("%d\n",res);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值