pta公路村村通最小生成树Kruskal并查集+路径压缩+按秩合并

现有村落间道路的统计数据表中,列出了有可能建设成标准公路的若干条道路的成本,求使每个村落都有公路连通所需要的最低成本。

输入格式:

输入数据包括城镇数目正整数N(≤1000)和候选道路数目M(≤3N);随后的M行对应M条道路,每行给出3个正整数,分别是该条道路直接连通的两个城镇的编号以及该道路改建的预算成本。为简单起见,城镇从1到N编号。

输出格式:

输出村村通需要的最低成本。如果输入数据不足以保证畅通,则输出−1,表示需要建设更多公路。

输入样例:

6 15
1 2 5
1 3 3
1 4 7
1 5 4
1 6 2
2 3 4
2 4 6
2 5 2
2 6 6
3 4 6
3 5 1
3 6 1
4 5 10
4 6 8
5 6 3

输出样例:

12

思路分析:

此题需最小生成树得到最小花费,利用Kruskal算法的并查集实现,其中使用路径压缩与按秩合并提高性能。

Kruskal: 核心思想对边按权重进行排序,按权重从小到大顺序取边来连通各分支,如果形成环(连通的点出现在同一并查集里)则舍去该边,直到所有点连通

!啊喂别被什么路径压缩,按秩合并高级到了,理解一下就行

便于理解视频并查集及其相关操作视频Union Find - Union and Find Operations​

以下为chatgpt生成范例:

顶点:0, 1, 2, 3, 4

边和权重:

边 0-1,权重 1
边 1-2,权重 2
边 2-3,权重 3
边 3-4,权重 4
边 0-4,权重 5

初始状态

  • parent 数组:[0, 1, 2, 3, 4] (每个顶点初始指向自己)
  • rank 数组:[0, 0, 0, 0, 0] (每个集合的秩初始为 0)

按权重排序的边

  • 顺序:0-1, 1-2, 2-3, 3-4, 0-4

遍历每条边

  1. 边 0-1 (权重 1):

    • 顶点 0 和 1 在不同的集合。
    • 合并这两个顶点。
    • parent 数组:[0, 0, 2, 3, 4]。
    • rank 数组:[1, 0, 0, 0, 0],因为合并两个秩为 0 的集合,新集合的秩变为 1。
  2. 边 1-2 (权重 2):

    • 顶点 1 和 2 在不同的集合。
    • 合并这两个顶点。
    • parent 数组:[0, 0, 0, 3, 4]。
    • rank 数组:[1, 0, 0, 0, 0],1-2 合并不会改变秩,因为顶点 1 已经是顶点 0 的子节点。
  3. 边 2-3 (权重 3):

    • 顶点 2 和 3 在不同的集合。
    • 合并这两个顶点。
    • parent 数组:[0, 0, 0, 0, 4]。
    • rank 数组:[1, 0, 0, 0, 0],同上,2-3 合并不会改变秩。
  4. 边 3-4 (权重 4):

    • 顶点 3 和 4 在不同的集合。
    • 合并这两个顶点。
    • parent 数组:[0, 0, 0, 0, 0]。
    • rank 数组:[1, 0, 0, 0, 0],同上,3-4 合并不会改变秩。
  5. 边 0-4 (权重 5):

    • 顶点 0 和 4 现在在同一个集合,忽略这条边。

结果

  • 选择了边 0-1, 1-2, 2-3, 和 3-4 构建最小生成树,总权重为 1 + 2 + 3 + 4 = 10。

AC代码:

#include<bits/stdc++.h>
using namespace std;
const int Max= 1000;
struct edge{
    int u,v,w;
    bool operator<(const edge &e)const{
        return w < e.w;         //sort函数排序会用到
    }
}e[3*Max];
int n,m;
int a[Max+1];
int ranks[Max+1];    //rank会引起歧义哦
int cost,num;		//cost->总花费 num->插入边数计数,树种边数=点数-1
void init(int a[]){
    for(int i = 1;i <= n;i ++)
        a[i] = i;   //初始化每个点为一个集
}
int find(int i){
    if(i!=a[i])
        a[i]=find(a[i]);
    return a[i];	    //压缩路径并查集,递归保持代码简洁性规避错误
    					//同一个连通集选取一个点表示集合
}
void unite(int i, int j){
    i=find(i);
    j=find(j);
    if(i!=j){
        if(ranks[i]<ranks[j])
            swap(i,j);     //保持小集合并到大集合中
        a[j]=i;
        if(ranks[i]==ranks[j])
            ranks[i]++;
    }
}
int main(){
    cin>>n>>m;
    init(a);
    for(int i = 0;i < m; i ++){
        cin>>e[i].u>>e[i].v>>e[i].w;		//输入
    }
    sort(e,e + m);          			//排序
    for(int i = 0;i < m;i ++){
        if(find(e[i].u)!=find(e[i].v)){  	//加边,如果不在同一集里,合并集
            cost += e[i].w;
            unite(e[i].u,e[i].v);
            num++;
        }
    }
    if(num != n - 1)cout<< -1;
    else cout<<cost;
    return 0;
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值