• 最小生成树的常用算法模板


    关于最小生成树的话,其实很早之前就接触了,当时也写了一篇关于最小生成树的文章,但一直没有好好刷题。

    接下来几天会持续更新维护KB-最小生成树专题

    最小生成树的算法没有其他算法那么复杂,算法思想比较简单,代码也比较容易。

    常见的最小生成树算法由Kruskal算法和Prim算法。

    1.Kruskal算法 -- 时间复杂度\(O(m * log m)\)

    算法思想:

    1. 建立一个并查集,每个点构成一个集合;
    2. 将边进行从小到大进行排序,依次扫描边edge(u,v,w);
    3. 如果u和v 属于同一个集合,那么跳过这轮循环;
    4. 如果不属于同一个集合,则把u、v合并为同一个集合;
    5. 当集合中的顶点数为n或者扫遍所有边edge,则构成最小生成树。
    #include<iostream> 
    #include<algorithm>
    #include<cstdio>
    using namespace std;
    const int manx=1e5+5; //对应顶点数目
    const int mamx=1e5+5; //对应边的数目
    int n,m,u,v,total=1;
    struct edge{
        int start,to;
        long long val;
    }bian[mamx];
    int a[manx];
    long long ans;
    int find(int x) //并查集
    {
        if(a[x]==x) return x;
        else return a[x]=find(a[x]);
    }
    bool cmp(edge x,edge y)
    {
        return x.val<y.val;
    }
    inline void kruskal()
    {
        for(int i=1;i<=m;i++)
        {
            u=find(bian[i].start);
            v=find(bian[i].to);
            if(u==v) continue; //如果两个点存在于同一个集合则跳过循环
            ans+=bian[i].val;
            a[u]=v;
            total++;
            if(total==n-1) break;
        }
    }
    int main()
    {
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++) //并查集的初始化,祖先为自己
            a[i]=i; 
        for(int i=1;i<=m;i++)
            scanf("%d%d%d",&bian[i].start,&bian[i].to,&bian[i].val);
        sort(bian+1,bian+1+m,cmp);
        kruskal();
        cout<<ans;
        return 0;
    }
    
    

    2.Prim算法

    Prim算法跟最短路中的Dijkstra 算法思想相近,有兴趣的可以了解一下:
    https://www.cnblogs.com/RioTian/p/12597634.html

    算法思想:
    1.把1作为起点加入最小生成树集合S;
    2.在未访问过的集合T中找出一点距离集合S最近的点,并在T中将其剔除,移入S中;
    3.重复2步骤,直到所有点加入S。

    朴素算法 时间复杂度 \(O(n^2)\)

    const int inf=2147483647;
    int a[manx][manx], d[manx], n, m, ans;
    bool vis[manx];
    void prim(){
    	for(int i=1;i<=n;i++) d[i]=inf,vis[i]=0,a[i][i]=0; //初始化各数组
    	d[1]=0; //1作为起点
    	for(int i=1;i<n;i++) //重复n-1次
    	{
    		int x=0;
    		for(int j=1;j<=n;j++)
    			if( !vis[j] && (x==0||d[j]<d[x]) ) //寻找未访问过且离集合S最近的点
    				x=j;
    		vis[x]=1;
    		for(int y=1;y<=n;y++)  //第一轮d[y]中由起点可到达的点得到更新
    			d[y]=min( d[y ], a[x][y]) //这里与Dij不同,因为Dij求的是两点间的距离,而这里是点到集合距离
    		}
    }
    

    Prim算法使用堆优化可达到与Kruscal一样的复杂度 \(O(m * log m)\)

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #include<queue>
    using namespace std;
    const int manx=5e3+5;
    const int mamx=1e5+5;
    int k,n,m,cnt,sum,a,b,c;
    int head[manx],dis[manx],vis[manx];
    struct node{
        int v,w,next;
    }e[mamx];
    typedef pair<int,int> p;
    priority_queue<p,vector<p>,greater<p> >q; //堆优化
    void add(int u, int v, int w) //链式前向星建图
    {
        e[++k].v=v;
        e[k].w=w;
        e[k].next=head[u];
        head[u]=k;
    }
    int main()
    {
        memset(dis,127,sizeof(dis));
        memset(head,-1,sizeof(head));
        scanf("%d%d",&n,&m);
        for(R i=1;i<=m;i++)
        {
            scanf("%d%d%d",&a,&b,&c);
            add(a,b,c);
          //无向图   add(b,a,c);
        }
        dis[1]=0; //一般将1作为最小生成树扩展的起点
        q.push(make_pair(0,1));
        while(!q.empty() && cnt<n)  //仔细观察可以发现堆优化的Prim也和堆优化的Dij的代码实现有很多相似之处,这是因为两者都是基于贪心的算法
        {
            int d=q.top().first,u=q.top().second;
            q.pop();
            if(vis[u]) continue;
            cnt++;  //计算S集合中顶点的个数
            sum+=d;  //计算最小权值和
            vis[u]=1; 
            for(R i=head[u];i;i=e[i].next)
                if(e[i].w<dis[e[i].v])
                    dis[e[i].v]=e[i].w,q.push(make_pair(dis[e[i].v],e[i].v));
        }
        printf("%d",sum);
    }	
    

    尽管堆优化,但不如直接使用Kruskal算法更加方便,因此,稀疏图用Kruskal,稠密图用Prim 。

  • 相关阅读:
    javascript概述
    linux系统编程(一)概述
    软件工程
    SQL
    数据结构和算法(一)概述
    cpp标准库
    c语言标准库
    c/c++概述
    编程语言的思考
    GCD学习
  • 原文地址:https://www.cnblogs.com/RioTian/p/13380742.html
Copyright © 2020-2023  润新知