还是畅通工程(并查集 + 图论 + prim/Kruskal(详细) + 最小生成树 + 最短路)

本文介绍了解决最小公路总长度问题的最小生成树概念及其两种构建方法:Prim算法和Kruskal算法。通过Kruskal算法实现的具体代码示例,展示了如何处理村庄间的距离数据,以确定连接所有村庄所需的最短公路总长度。

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

原题链接

Problem Description

某省调查乡村交通状况,得到的统计表中列出了任意两村庄间的距离。省政府“畅通工程”的目标是使全省任何两个村庄间都可以实现公路交通(但不一定有直接的公路相连,只要能间接通过公路可达即可),并要求铺设的公路总长度为最小。请计算最小的公路总长度。

Input

测试输入包含若干测试用例。每个测试用例的第1行给出村庄数目N ( < 100 );随后的N(N-1)/2行对应村庄间的距离,每行给出一对正整数,分别是两个村庄的编号,以及此两村庄间的距离。为简单起见,村庄从1到N编号。当N为0时,输入结束,该用例不被处理。

Output

对每个测试用例,在1行里输出最小的公路总长度。

Sample Input

3
1 2 1
1 3 2
2 3 4
4
1 2 1
1 3 4
1 4 1
2 3 3
2 4 2
3 4 5
0

Sample Output

3
5

最小生成树

  • G= (V,E),若G的一个生成子图是一棵树,则称之为G的一棵生成树(记为T)。
  • 最小生成树:无向图G的所有生成树中,树枝的权值总和最小的称为G的最小生成树

prim算法

  • 算法思想:将各个点一一加入树中

  • 具体做法:

  • 先选择一个顶点作为树的根节点,把这个根节点当成一棵树

  • 选择图中距离这棵树最近但是没有被树收录的一个顶点,把他收录在树中,并且保证不构成回路

  • 按照这样的方法,把所有的图的顶点一一收录进树中。

  • 如果没有顶点可以收录:

    ①如果图中的顶点数量等于树的顶点数量–>最小生成树构造完成

    ②如果图中的顶点数量不等于树的顶点数量–>此图不连通

在这里插入图片描述
对于上图,依次将元素1 -> 3 -> 6 -> 4 -> 2 -> 5加入集合可得到其最小生成树为:
在这里插入图片描述
再如使用prim算法的思想,依次将a -> b -> c -> i -> f -> g -> h-> d -> e点加入集合中,可构成如下图所示最小生成树:

在这里插入图片描述

Kruskal算法

  • kruskal算法思想:
  • 贪心: 先将所有的点加入集合,然后依次选取最短的边来组成一棵最小的生成树。n个顶点共有n - 1条边。
  • 具体做法:
    ①将n个顶点看成n个集合。
    按权值由小到大的顺序选择边(先按照权值排序)且所选边应满足两个顶点不在同一个顶点集合内(不构成回路),将该边放到生成树边的集合中。同时将该边的两个顶点所在的顶点集合合并。
    ③重复②,直到所有的顶点都在同一个顶点集合内。
    可以使用并查集判断两个顶点是否在同一个顶点集合内

在这里插入图片描述
例,对于上图,使用Kruskal算法(依次加入边1-3,4-6,2-5,3-6,2-3),可以构成如下图所示的最小生成树:

在这里插入图片描述

样例分析

  • 样例1 输出1 + 2 = 3

在这里插入图片描述

  • 样例2 输出1 + 1 + 3 = 5
    在这里插入图片描述

由以上两图不难发现,计算最小的公路总长度,采用Kruskal算法构建最小生成树即可

AC代码(kruskal算法)

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

using namespace std;

const int N = 105;
const int M = 5010;
int parent[N];//disjoint set

struct edge
{
    int a;//village a
    int b;//village b
    int dis;//distance of a and b
}nums[M];

bool cmp(edge u,edge v)
{
    return u.dis < v.dis;//distance : from small to big
}

int find(int x)//disjoint set's find
{
    if (x != parent[x]) parent[x] = find(parent[x]);
    return parent[x];
}

bool Union(int x,int y)//disjoint set's union
{
    int a = find(x);
    int b = find(y);
    if (a != b)
    {
        parent[a] = b;
        return true;//union succesefully
    }
    return false;//union false
}

int main()
{
    int n;
    while (scanf("%d",&n) != EOF && n)
    {
        for (int i = 1;i <= n;i++) parent[i] = i;//init disjoint set
        int m = n * (n - 1) / 2;
        
        for (int i = 1;i <= m;i++) scanf("%d%d%d",&nums[i].a,&nums[i].b,&nums[i].dis);//read data
        
        sort(nums + 1,nums + 1 + m,cmp);
        
        //Kruskal algorithm
        int distance = 0;
        int cnt = 0;//计数器:集合合并成功的次数
        for (int i = 1;i <= m;i++)
        {
            if (find(nums[i].a) != find(nums[i].b))//当前两个村庄不连通(不在同一个集合)
            {
                Union(nums[i].a,nums[i].b);//连接两个村庄(集合)
                distance += nums[i].dis;//距离加上当前选择的公路的权值
                cnt ++;
                
            }
        }
        
        if (cnt == n - 1)//n个顶点的需要合并n - 1条边 
        {
            //注意不能用i == n - 1来判断,因为i并不一定等于集合合并成功的次数
            printf("%d\n",distance);
            continue;
        }
    }
    
    return 0;
}

AC代码2

不需要用ctr == n-1来判断,可以直接打印distance

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

using namespace std;

const int N = 105;
const int M = 5010;
int parent[N];//disjoint set

struct edge
{
    int a;//village a
    int b;//village b
    int dis;//distance of a and b
}nums[M];

bool cmp(edge u,edge v)
{
    return u.dis < v.dis;//distance : from small to big
}

int find(int x)//disjoint set's find
{
    if (x != parent[x]) parent[x] = find(parent[x]);
    return parent[x];
}

bool Union(int x,int y)//disjoint set's union
{
    int a = find(x);
    int b = find(y);
    if (a != b)
    {
        parent[a] = b;
        return true;//union succesefully
    }
    return false;//union false
}

int main()
{
    int n;
    while (scanf("%d",&n) != EOF && n)
    {
        for (int i = 1;i <= n;i++) parent[i] = i;//init disjoint set
        int m = n * (n - 1) / 2;
        
        for (int i = 1;i <= m;i++) scanf("%d%d%d",&nums[i].a,&nums[i].b,&nums[i].dis);//read data
        
        sort(nums + 1,nums + 1 + m,cmp);
        
        //Kruskal algorithm
        int distance = 0;
        for (int i = 1;i <= m;i++)
        {
            if (find(nums[i].a) != find(nums[i].b))//当前两个村庄不连通(不在同一个集合)
            {
                Union(nums[i].a,nums[i].b);//连接两个村庄(集合)
                distance += nums[i].dis;//距离加上当前选择的公路的权值
            }
        }
        
        //就算Union了所有边,distance也只计入了最小生成树的边(边不在同集合)
        printf("%d\n",distance);
    }
    
    return 0;
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值