最小生成树(Prim算法、Kruskal算法)

目录:

题目描述:

Kruskal算法:

代码及分析: 

Prim算法:

代码及分析:

样例:

题目描述:

如题,给出一个无向图,求出最小生成树,如果该图不连通,则输出 orz

输入格式:

第一行包含两个整数 N, M,表示该图共有 N 个结点和 M 条无向边。

接下来 M 行每行包含三个整数 Xi​, Yi​, Zi​,表示有一条长度为 Zi​ 的无向边连接结点 Xi​, Yi​。

输出格式:

如果该图连通,则输出一个整数表示最小生成树的各边的长度之和。如果该图不连通则输出 orz

Kruskal算法:

将所有的边按权值的大小从小到大进行排序,选取权值最小的边,回贴到图中并判断是否形成了环,若形成了环,则丢弃该边,继续下一条边的回贴,若没有形成环,则递归调用,继续下条边的判断。此时的判断有没有形成环,可以用有没有公共祖先进行判断,若存在公共祖先,则会形成环,否则不会,详情见代码。

代码及分析: 

#include <bits/stdc++.h>
using namespace std;
const int maxn = 5e5 + 10;
const int maxv = 2e5 + 10;
int n, m, ans, f[maxv];
//因为该图为无向图,故存储时需要将数组长度扩大一倍
struct edge{
    int u, v, w;
}g[maxn];

bool cmp(edge a,edge b) {
    return a.w < b.w;
}

int find(int a) {
    if (f[a] == a)
        return a;
    return f[a] = find(f[a]);
}

void construct() {
    int cnt = 0;
    for (int i = 1; i <= n; i++)
        f[i] = i;
    for (int i = 1; i <= m; i++) {
        int fx = find(g[i].u);
        int fy = find(g[i].v);
        //该步骤是为了判断是否形成了环
        if (fy != fx) {
            f[fx] = fy;
            ans += g[i].w;
            cnt++;
        }
        if (cnt == n - 1) {
            printf("%d", ans);
            break;
        }
    }
    if(cnt != n - 1)
        printf("orz");
}

int main() {
    scanf("%d %d", &n, &m);
    for (int i = 1; i <= m; i++)
        scanf("%d %d %d", &g[i].u, &g[i].v, &g[i].w);
    sort(g + 1, g + 1 + m, cmp);
    construct();
    return 0;
}

Prim算法:

首先从某个点出发,找到与之相连的最小权值的边,并将其回贴到图中,然后在此时的最小生成树中,找到某点与(还未加入最小生成树中的某点)形成的最小权值的边,并将其回贴到图中,如此递归形成最后的结果,详情见代码。

代码及分析:

#include<bits/stdc++.h>
using namespace std;
const int INF = 1e9 + 10;
const int maxn = 5010;
const int maxm = 2e5 + 10;

struct edge{
    int v, w, next;
}e[maxm * 2];
//cnt表示有向边的条数
//tot表示已连接的最小生成树的边
//now代表此时准备连接哪个点
//ans表示结果
int cnt, n, m, tot, now = 1, ans;
//head表示某点作为前驱节点时的编号
//dis表示已经加入最小生成树的点到没有加入的点的最小距离
//vis判断某个点是否已经加入最小生成树
int head[maxn], dis[maxn], vis[maxn];

void add(int u, int v, int w) {
    e[++cnt].v = v;
    e[cnt].w = w;
    e[cnt].next = head[u];
    head[u] = cnt;
}

void prim() {
    for(int i = 2; i <= n; ++i)
        dis[i] = INF;
    //注意重边
    //找出所有与1相连的点,记录两点间的最短距离
    for(int i = head[1]; i; i = e[i].next)
        dis[e[i].v] = min(dis[e[i].v], e[i].w);
    //最小生成树边数等于点数 - 1
    while(++tot < n) {
        int minn = INF;
        //标记走过的点
        vis[now] = 1;
        int t = now;
        //枚举每一个没有使用的点
        //找出最小值作为新边
        for(int i = 1; i <= n; ++i)
            if(!vis[i] && minn > dis[i]) {
                minn = dis[i];
                now = i;
            }
        //如果最新的结点未被修改,则无法形成最小生成树
        //故该图不连通,则需输出orz
        if(now == t) {
            printf("orz");
            return;
        }
        ans += minn;
        //枚举now的所有连边,更新dis数组
        for(int i = head[now]; i; i = e[i].next) {
            int v = e[i].v;
            if(dis[v] > e[i].w && !vis[v])
                dis[v] = e[i].w;
        }
    }
    printf("%d", ans);
}

int main() {
    scanf("%d %d", &n, &m);
    int u, v, w;
    for(int i = 1; i <= m; ++i) {
        scanf("%d %d %d", &u, &v, &w);
        //该图为无向图
        //故需要当作两条边加入结构体中
        add(u, v, w);
        add(v, u, w);
    }
    prim();
    return 0;
}

样例:

输入:

4 5

1 2 2

1 3 2

1 4 3

2 3 4

3 4 3

输出:

7

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值