畅通工程 1863 Prim算法 和 Kruskal算法

本文探讨了在给定村庄间的道路成本后,如何利用Prim和Kruskal算法找到连接所有村庄所需的最低成本,涉及无向图上的最小生成树构建。

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

 

畅通工程

Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 43016    Accepted Submission(s): 19231


 

Problem Description

省政府“畅通工程”的目标是使全省任何两个村庄间都可以实现公路交通(但不一定有直接的公路相连,只要能间接通过公路可达即可)。经过调查评估,得到的统计表中列出了有可能建设公路的若干条道路的成本。现请你编写程序,计算出全省畅通需要的最低成本。

 

 

Input

测试输入包含若干测试用例。每个测试用例的第1行给出评估的道路条数 N、村庄数目M ( < 100 );随后的 N
行对应村庄间道路的成本,每行给出一对正整数,分别是两个村庄的编号,以及此两村庄间道路的成本(也是正整数)。为简单起见,村庄从1到M编号。当N为0时,全部输入结束,相应的结果不要输出。

 

 

Output

对每个测试用例,在1行里输出全省畅通需要的最低成本。若统计数据不足以保证畅通,则输出“?”。

 

 

Sample Input

 

3 3 1 2 1 1 3 2 2 3 4 1 3 2 3 2 0 100

 

 

Sample Output

 

3 ?

 

 

Source

浙大计算机研究生复试上机考试-2007年

 

 

Recommend

lcy   |   We have carefully selected several similar problems for you:  1879 1233 1875 1232 1102 

Prim

Prim算法用于构建最小生成树——即树中所有边的权值之和最小。例如,构建电路板,使所有边的和花费最少。只能用于无向图。因为最终求的是边到树集合的距离,树集合中的顶点无法再次遍历,所以只能应用与无向图

将顶点合成一颗树

//#include<bits/stdc++.h>
#include<cstdio>
#include<algorithm>
#include <string.h>
using namespace std;
#define inf 0x3f3f3f3f
#define M 105
int n,m;
int mat[M][M];
int prim(int m,int sta,int mat[][M]){//n表示有n个顶点,sta表示从sta这个顶点出发生成最小生成树 M表示存放的无向图 
    int sum=0;
    int sum=0;
    int i,j,flag;
    int lowcost[M];
    int u[M];//0表示不在树中,1表示在树中 s 
    //初始化
    for(i=0;i<m;i++){
        lowcost[i]=mat[sta][i];    // lowcost[i]记录图中的点到树顶点的最小权值 因为初始时只有一个点
        u[i]=0;
    }
    u[sta]=1;//此时将sta加入树中
        //因为最小树中会包含每个点,而每一次扫描会加入一条边,树集合中已经有一个初始顶点,所以这里扫描m-1次 
    for(i=1;i<m;i++){
        //扫描到树中的顶点最小权值的点 使下一步能够将该点加入到树u中
        int min=inf;//使min此时无穷大 
        for(j=0;j<m;j++)
            if(!u[j]&&min>lowcost[j])
                min=lowcost[j],flag=j;
    
        if(min==inf) //此种情况图未联通,未联通的值为inf 
            return 0;
        sum+=min;//记录最小的权值边 会记录顶点数量-1次 
        u[flag]=1;//将最小的点加入树集合中 
        //因为此时加入了新的树结点,所以lowcost的值也需要改变成到树顶点的最小权值
        for( j=0;j<m;j++)
            if(mat[flag][j]<lowcost[j])
                lowcost[j]=mat[flag][j];
         
    } 
        return sum;
}
int main(){
    
    while(scanf("%d%d",&n,&m)&&n){
        memset(mat,inf,sizeof(mat));
        for(int i=0;i<n;i++){
            int from,to,cost;
            scanf("%d%d%d",&from,&to,&cost);
            mat[from-1][to-1]=mat[to-1][from-1]=cost;
        }
        int ret=prim(m,0,mat);
        if(ret==0){
            printf("?\n");
        }else{
            printf("%d\n",ret);    
        }         
    }
    return 0;
}

Kruskal

将森林合并成一个mnt(最小树)

使用并查集,关键是要将边排序

//#include<bits/stdc++.h>
#include<cstdio>
#include<algorithm>
using namespace std;
#define maxsize 10001
int biaoji[maxsize];
int set2[maxsize];
int n,m; //n表示道路(边) m表示(顶点)村庄数目 
struct edge{
    int from,to;
    long long cost;
}E[maxsize];

bool cmp(edge a,edge b){
    return a.cost<b.cost;
}
void init(){
    for(int i=0;i<maxsize;i++)
            set2[i]=i;
}
int find(int i){
    return set2[i]==i?i:set2[i]=find(set2[i]);
}
void merge(int a,int b){
    if(find(a)==find(b))
        return;
    set2[find(a)]=find(b);
}
bool check(int a,int b){
    return find(a)==find(b);
}
long long kruskal(){
    long long reg=0;
    sort(E+1,E+1+n,cmp);
    for(int i=1;i<=n;i++){
        if(check(E[i].from,E[i].to)) continue;
        merge(E[i].from,E[i].to);
        reg+=E[i].cost;
    }
    return reg;
}
int main(){
    while(scanf("%d%d",&n,&m)==2){
        if(n==0)
            break;
        init();
        for(int i=1;i<=n;i++){
            scanf("%d%d%lld",&E[i].from,&E[i].to,&E[i].cost);
        }
        long long ret=kruskal();
        for(int i=1;i<=m;i++)
            if(!check(1,i))
                ret=-1;    
        if(ret==-1)    
            printf("?\n");
        else
            printf("%lld\n",ret);        
        
    }
    return 0;
} 
### Prim算法Kruskal算法的比较 #### 工作原理的不同 Prim算法是从任意一个顶点开始构建最小生成树,逐步扩展已有的部分直到覆盖所有顶点。每次迭代过程中选取当前未被纳入生成树集合中的离已有结构最近的一个节点并将其加入其中[^4]。 相比之下,Kruskal算法则侧重于处理边而非顶点。该方法首先按照权值升序排列所有的边,在遍历这些有序列表的同时尝试添加每条边至正在形成的森林里——只要这样做不会形成环即可[^5]。 #### 数据结构的选择 对于Prim算法而言,通常会利用优先队列来高效找到下一个要访问的最佳候选者;而在实现上可以借助二叉堆或是斐波那契堆等高级数据结构进一步优化性能表现[^3]。 另一方面,由于Kruskal算法涉及频繁查询两个端点是否属于同一连通分量的操作,因此一般配合不相交集(Union-Find)的数据结构使用以加速此类判断过程[^1]。 #### 时间复杂度分析 当面对稠密图时,即边的数量接近于\(V^2\)的情况之下,Prim算法能够展现出更优的时间效率,特别是通过邻接矩阵表示法下的朴素版本更是如此。然而,在稀疏图的情况下,则应考虑采用基于边表表达形式的Kruskal方案,其主要开销在于初始阶段对全部边实施排序操作\[O(E\log E)\]。 #### 应用场景建议 针对具体的应用环境选择合适的最小生成树算法至关重要: - 对于较为密集的网络拓扑结构,推荐选用Prim算法; - 如果待解决问题所对应的图形呈现明显稀疏特征,则更适合应用Kruskal算法来进行求解。 ```cpp // 示例代码展示两种算法的核心逻辑差异 (C++) // Prim Algorithm Core Logic void prim(vector<vector<int>>& graph, int start){ priority_queue<pair<int,int>, vector<pair<int,int>>, greater<>> pq; vector<bool> visited(graph.size(), false); pq.push({0,start}); while(!pq.empty()){ auto [weight,node]=pq.top(); pq.pop(); if(visited[node]) continue; visited[node]=true; // Process node and add adjacent nodes to the queue... } } // Kruskal Algorithm Core Logic struct Edge { int u,v,w; }; bool cmp(const Edge& a,const Edge& b){return a.w<b.w;} int find(int x,vector<int>& parent); void kruskal(vector<Edge>& edges, int n){ sort(edges.begin(),edges.end(),cmp); vector<int>parent(n),rank(n,0); iota(parent.begin(),parent.end(),0); for(auto &e : edges){ int pu=find(e.u,parent),pv=find(e.v,parent); if(pu!=pv){ union_sets(pu,pv,parent,rank); // Add edge e into MST ... } } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值