最小生成树模板 洛谷 Prim算法和Kruskal算法

2023-05-16

洛谷-最小生成树模板

题目描述

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

输入格式

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

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

输出格式

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

输入输出样例

输入 

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

输出 

7

最小生成树MST有两种基本的算法:Kruskal算法Prim算法


首先介绍Kruskal算法:

它的基本思想为:将所有边按照权值的大小进行升序排序,然后从小到大一一判断,条件为:如果这个边不会与之前选择的所有边组成回路,就可以作为最小生成树的一部分;反之,舍去。直到具有 n 个顶点的连通网筛选出来 n-1 条边为止。筛选出来的边和所有的顶点构成此连通网的最小生成树。

 

要使最小生成树边权值之和最小,树的每一条边权值应尽量小。因此,有以下几个步骤:

假设连通网N=V,{E}),T=(V,{TE})表示N的最小生成树,TE为最小生成树上边的集合。初始时令TE为空集。

Step1:令最小生成树T的初态为只有n个顶点的非连通图T=(V,{TE})TE={}

Step2:从权值最小的边(u,v)开始,若该边依附的两个顶点落在T的不同连通分量上,则将此边加入到TE中,即TE=TE∪(u,v),否则舍弃此边,选择下一条代价最小的边。 

Step3:重复Step2,直至TE所有顶点在同一连通分量上。此时T=(V,{TE})就是N的一棵最小生成树。        

 

 判断是否有回路则使用到了并查集的概念。

并查集主要用来解决判断两个元素是否属于同一个集合,以及把两个集合合并的一种数据结构。并查集初始时把每一个对象看作是一个单元素集合;然后依次按顺序读入等价对后,将等价对中的两个元素所在的集合合并。在此过程中将重复地使用一个搜索(find)运算,确定一个元素在哪一个集合中。当读入一个等价对A≡B时,先检测A和B是否同属一个集合,如果是,则不用合并;如果不是,则用一个合并(union)运算把A、B所在的集合合并,使这两个集合中的任两个元素都是等价的。因此,并查集在处理时主要有搜索和合并两个运算。

假如现在有如下的连通图:

要求它的最小生成树,则步骤为:

 

 

 

 Kurskal算法的时间复杂度为:O(e*loge)只和边数有关,与网中的顶点数无关,适合求边稀疏的网的最小生成树

代码具体实现如下:

  1. 输入边,用结构体储存
  2. 用结构体快排以边比较从小到大快排
  3. 建一个并查集,并初始化并查集(并查集代表两个点有没有在同一个树里面

设边edges[100000],edges.start一个点,edges.to另一个点,edge.weight是边长,total是最终答案。

  1. for(i=1;i<=m(边数);i++)找一条边edges[i],若edges[i].start与edges[i].to不在同一个并查集里面,就将edges[i].start与edges[i].to所在的并查集合并,并将total+=edge[i].weight。
  2. 若在同一个并查集,则跳过这次循环。因为如果这两个点连接起来,就会形成一个环。

注意:当到了已连边的个数是点的个数-1时,就要停止循环,因为这个时候,最小生成树已经完成了,所有的并查集都连在了一起。

#include<bits/stdc++.h>
using namespace std;

struct edge{
    int start;
    int to;
    long long weight;
}edges[200100];
int f[200005];
int m,n,i,j,u,v,total;
long long result;

//并查集
int finds(int x){
    if(x==f[x]){
        return x;
    }else{
        f[x]=finds(f[x]);
        return f[x];
    }
}

void createMinSpanTreeKruskal(){
    for(i=1;i<=m;i++){
        u=finds(edges[i].start);
        v=finds(edges[i].to);
        if(u==v){
            continue;
        }
        f[u]=v;
        result+=edges[i].weight;
        total++;
        if(total==n-1){
            break;
        }
    }
}

bool cmp(edge e1,edge e2){
    return e1.weight<e2.weight;
}

int main()
{
    scanf("%d %d",&n,&m);
    for(i=1;i<=n;i++){
        f[i]=i;
    }
    for(i=1;i<=m;i++){
        scanf("%d %d %d",&edges[i].start,&edges[i].to,&edges[i].weight);
    }
    //先将edges按照边的权重升序排列
    sort(edges+1,edges+m+1,cmp);
    createMinSpanTreeKruskal();
    if(total==n-1){
        printf("%lld",result);
    }else{
        printf("orz");
    }
    return 0;
}

 


接下来介绍Prim算法

Prim算法的基本思想:首先以一个结点作为最小生成树的初始结点,然后以迭代的方式找出与最小生成树中各结点权重最小边,并加入到最小生成树中。加入之后如果产生回路则跳过这条边,选择下一个结点。当所有结点都加入到最小生成树中之后,就找出了连通图中的最小生成树了。(有点像求最短路径里面的迪杰斯特拉算法)

Prim算法的基本步骤:

N=(V,{E})是连通网,T=(V,{TE})表示N的最小生成树,TE为最小生成树边的集合,初始为空集。则Prim算法的执行过程:        

Step1:U={u}u∈Vu是网中任意一个顶点),TE={}

Step2:u∈Uv∈V-U的边(u,v)∈E中寻找一条代价最小的边(u,v)并入TE,同时将顶点v并入U

Step3:重复Step2,直至U=V,此时TE中必有n-1条边,而T={V,{TE}}N的一棵最小生成树。

 

 先还假设上面Kruskal的连通图:

 要求它的最小生成树,则步骤为:

 

 

 

至此,得到最小生成树。

Prim算法的时间复杂度为:O(n*n),只和顶点数有关,与网中的边数无关。适用于求边稠密的最小生成树。

实现代码如下:

#include<bits/stdc++.h>
using namespace std;

const int INF=INT_MAX;
int n,m;
int w[5005][5005];//用于记录所有边的关系
int lowcost[5005];//用于记录距离树的距离最短路程

void createMinSpanTreePrim(){
    int i,j,sum=0,minn,minIndex;
    //初始化最小花费数组,默认先把离1点最近的找出来放好
    for(i=1;i<=n;i++){
        lowcost[i]=w[1][i];
    }
    lowcost[1]=0;   //记录1已经被访问过了,所以lowcost变为0
    for(i=1;i<n;i++){   //1已经访问过了,所以循环n-1次
        minn=INF;
        for(j=1;j<=n;j++){
            if(lowcost[j] && lowcost[j]<minn){  //该节点没有被访问过且代价小于minn
                minn=lowcost[j];    //对于最小值赋值
                minIndex=j;         //对于最小值的下标赋值
            }
        }
        lowcost[minIndex]=0;    //记录这个点已经被访问过了
        sum+=minn;
        for(j=1;j<=n;j++){  //如果这点没有被访问过,而且这个点到任意一点的距离比现在到树的距离近那么更新
            if(w[minIndex][j]<lowcost[j])
                lowcost[j]=w[minIndex][j];
        }
    }
    //检测如果有lowcost的值不为0,则说明该图不是连通图
    for(i=1;i<=n;i++){
        if(lowcost[i]!=0){
            printf("orz");
            return;
        }
    }
    printf("%d\n",sum);
}

int main(){
    int t1,t2,t3;
    scanf("%d %d",&n,&m);
    //初始化maps,均为INF。访问过之后变为0
    for(int i=0;i<=n;i++){
        for(int j=0;j<=n;j++){
             w[i][j]=INF;
        }
    }
    //无向图,对称
    for(int i=1;i<=m;i++){
        scanf("%d %d %d",&t1,&t2,&t3);
        if(t3<w[t1][t2]){
            w[t2][t1]=w[t1][t2]=t3;
        }
    }
    createMinSpanTreePrim();
    return 0;
}

Prim算法和Kruskal算法对比:

从策略上来说,Prim算法是直接查找,多次寻找邻边的权重最小值,而Kruskal是需要先对权重排序后查找的。

所以说,Kruskal在算法效率上是比Prim快的,因为Kruskal只需一次对权重的排序就能找到最小生成树,而Prim算法需要多次对邻边排序才能找到。

这点也能从洛谷评判时间上看出来。(当然,在这里写的Prim算法属于最基本的无优化的算法,耗时巨长,也贼费空间。大佬们的各种牛皮算法如优先队列+堆优化等,本蒟蒻还得多加学习😭)

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

最小生成树模板 洛谷 Prim算法和Kruskal算法 的相关文章

  • ChatGPT 生成的代码比你写的更不安全

    出品 OSC开源社区 xff08 ID xff1a oschina2013 ChatGPT 是 OpenAI 发布的大语言模型聊天机器人 xff0c 可以根据用户的输入生成文本 xff0c 其中包括代码 不过加拿大魁北克大学的研究人员发现
  • 刺激!ChatGPT给我虚构了一本书?

    ChatGPT很强大 xff0c 可以帮我们处理很多问题 xff0c 但这些问题的答案的正确性您是否有考证过呢 xff1f 昨晚 xff0c DD就收到了一个有趣的反馈 xff1a 提问 xff1a 有什么关于数据权限设计的资料推荐吗 xf
  • 把中国法律法规喂给 ChatGPT = AI 法律助手

    关于ChatGPT的各种工具层出不穷 xff0c AI写作 AI绘画这些已经卷成x了 xff0c 今天给大家开个新眼界 下面这位大佬把中国法律法规投喂给ChatGPT xff0c 做了一个 xff1a 中国法律AI助手 一起看看演示 xff

随机推荐