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

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

接下来几天会持续更新维护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 。

原文地址:https://www.cnblogs.com/RioTian/p/13380742.html