【例4-11】最短网络(agrinet)(信息学奥赛一本通- P1350)

【题目描述】

农民约翰被选为他们镇的镇长!他其中一个竞选承诺就是在镇上建立起互联网,并连接到所有的农场。当然,他需要你的帮助。约翰已经给他的农场安排了一条高速的网络线路,他想把这条线路共享给其他农场。为了用最小的消费,他想铺设最短的光纤去连接所有的农场。你将得到一份各农场之间连接费用的列表,你必须找出能连接所有农场并所用光纤最短的方案。每两个农场间的距离不会超过100000。

【输入】

第一行:农场的个数,N(3≤N≤100)。

第二行..结尾:后来的行包含了一个N×N的矩阵,表示每个农场之间的距离。理论上,他们是N行,每行由N个用空格分隔的数组成,实际上,他们限制在80个字符,因此,某些行会紧接着另一些行。当然,对角线将会是0,因为不会有线路从第i个农场到它本身。

【输出】

只有一个输出,其中包含连接到每个农场的光纤的最小长度。

【输入样例】

复制代码
4
0 4 9 21
4 0 8 17
9 8 0 16
21 17 16 0

【输出样例】

复制代码
28

1. 算法选型与分析

这是一道标准的 最小生成树 问题。

题目给出的数据规模非常小 (N<=100),且输入格式为邻接矩阵(稠密图)。

我们有三种主流解法:(这里只写两种,堆优化的prim前几篇都有写,想看的可以直接去看我真对洛谷p3366的题解)

  1. 朴素 Prim 算法 (推荐)

    • 原理:以点为核心,每次寻找离集合最近的点。

    • 复杂度:O(N^2)。

    • 优势 :非常适合稠密图邻接矩阵输入。对于N=100,运算量仅10000,效率极高,且代码不需要额外的结构体和排序,最简洁。

  2. Kruskal 算法

    • 原理:以边为核心,排序后贪心选取不构成环的边。

    • 复杂度:O(M log M)。

    • 注意:由于输入是矩阵,边数M约等于N^2。虽然排序略耗时,但对于本题的数据规模依然能轻松AC。

    • 技巧:在读取矩阵时,可以只读取j>i的部分(右上半矩阵),这样既过滤了自环,又去除了重复边,减少了一半的存储和排序压力。


2. 解法一:朴素 Prim 算法 (邻接矩阵)

这是解决本题理论最优 的方法。我们维护 dis 数组表示每个点到当前生成树集合的最短距离。

cpp 复制代码
//这道题最优选择朴素prim 复杂度O(N^2)
#include <iostream>
#include <cstring>//对应memset
using namespace std;
int n;
int g[110][110];
int dis[110];//每个点到集合的距离
int vis[110];//标记每个点是否已经被连接上(加入集合)
int pre[110];
long long sum;//最小生成树的长度

void prim(int s){
    dis[s]=0;//起点到自己距离为0
    for(int i=1;i<=n;i++){//n个点需要加入集合
        int p=0;
        //在未加入集合的农场中找距离集合最近的农场
        for(int j=1;j<=n;j++){
            if(vis[j]==0 && dis[j]<dis[p])
                p=j;
        }
        //连通性检查
        //如果已经不存在可以连通的农场就退出
        if(p==0 || dis[p]==0x3f3f3f3f) break;
        vis[p]=1;//找到了就标记加入集合
        sum+=dis[p];//把p到集合的距离加入最小生成树长度
        //用p点去尝试更新所有p的未被点亮的临接点
        for(int j=1;j<=n;j++){
            //如果该邻接点未加入集合且经过p点到集合的距离小于目前
            //自身到集合的距离 就更新该邻接点到集合的距离
            if(vis[j]==0 && dis[j]>g[p][j]){
                dis[j]=g[p][j];
                //j点被p更新到集合的最短距离,前驱就是p
                //因为目前来看j经过p到集合距离最短
                pre[j]=p;
            }
        }
    }
}
int main(){
    cin>>n;
    memset(dis,0x3f,sizeof(dis));//初始化每个点到集合的距离为无穷
    //邻接矩阵存图
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            //虽然是无向边,但是输入数据为邻接矩阵
            //所以不需要再去双向加边
            cin>>g[i][j];
        }
    }
    prim(1);//从1号点开始
    cout<<sum;
}

3. 解法二:Kruskal 算法 (输入优化版)

使用并查集维护连通性。

关键点:在读取矩阵时,必须先 cin>>x读取数据,然后再判断 if(j>i)决定是否存储。如果直接在if里cin,会导致输入错位(吞掉数据)。

cpp 复制代码
//kruskal
#include <iostream>
#include <algorithm>//对应sort函数
using namespace std;
struct edge{
    int u,v,w;
    //按边权从小到大排序
    friend bool operator<(edge a,edge b){
        return a.w<b.w;
    }
};
edge e[10010];//边集数组
edge mst[10010];//记录最小生成树的边(记录网络线路)
int cnt1;//记录边集数组的边数
int cnt2;//记录最小生成树边数
int n;
int fa[110];//记录每个点在集合中的老大
long long sum;//记录最小生成树的总长度(光纤的最小长度)

//并查集+查询
int find(int x){
    if(fa[x]==x) return x;//如果已经是根结点,就返回
    //否则递归找根节点,并把根节点赋给沿途所有节点
    return fa[x]=find(fa[x]);
}

void uni(int a,int b){
    int faa=find(a);//找a老大
    int fab=find(b);//找b老大
    if(faa!=fab){//如果老大相同无事发生
        fa[fab]=faa;//不同就让a老大变成b老大的老大
    }
}

void kruskal(){
    for(int i=1;i<=cnt1;i++){
        int a=e[i].u;//第i条边的一个端点
        int b=e[i].v;//第i条边的另外一个端点
        if(find(a)!=find(b)){//如果这两个端点不在一个集合里(不连通)
            cnt2++;//就把这两个端点脸上,然后边数记录+1
            sum+=e[i].w;//更新最小生成树长度
            mst[cnt2].u=a;
            mst[cnt2].v=b;
            mst[cnt2].w=e[i].w;
            uni(a,b);
        }
    }
}

int main(){
    cin>>n;
    //储存边集数组
    for(int i=1;i<=n;i++) fa[i]=i;//初始化每个点自成集合(老大是自己)
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            int x;
            cin>>x;//第i条边边权
        //只存j大于i的边,这样可以少存一半重复边以及自环边(虽然全存进去也没事)
            if(j>i){
                e[++cnt1].u=i;
                e[cnt1].v=j;
                e[cnt1].w=x;
            }
        }
    }
    //对边集数组按边权从小到大排序
    sort(e+1,e+cnt1+1);
    kruskal();
    cout<<sum;
    return 0;
}

4. 总结

  • 朴素 Prim:针对N<=100的矩阵输入题,它是最稳健、代码量最少的选择。

  • Kruskal :通用性强,利用 if(j>i) 的技巧可以很好地处理矩阵输入,去除冗余边。

  • 易错点 :Prim别忘了头文件#include <cstring>(或者写万能头);Kruskal读取矩阵时一定要先读入再判断

相关推荐
方圆工作室2 小时前
【C语言图形学】用*号绘制完美圆的三种算法详解与实现【AI】
c语言·开发语言·算法
Lips6112 小时前
2026.1.16力扣刷题
数据结构·算法·leetcode
kylezhao20193 小时前
C# 文件的输入与输出(I/O)详解
java·算法·c#
CodeByV3 小时前
【算法题】堆
算法
kaikaile19953 小时前
A星算法避开障碍物寻找最优路径(MATLAB实现)
数据结构·算法·matlab
今天_也很困4 小时前
LeetCode 热题100-15.三数之和
数据结构·算法·leetcode
企业对冲系统官4 小时前
基差风险管理系统日志分析功能的架构与实现
大数据·网络·数据库·算法·github·动态规划
ldccorpora4 小时前
GALE Phase 1 Chinese Broadcast News Parallel Text - Part 1数据集介绍,官网编号LDC2007T23
人工智能·深度学习·算法·机器学习·自然语言处理
千金裘换酒4 小时前
LeetCode 数组经典题刷题
算法·leetcode·职场和发展