最小生成树(Prim+Kruskal)算法

关于生成树和最小生成树:

  • 生成树:一个连通图的生成树是一个极小连通子图,其中含有图中的全部顶点,和构成一棵树的(n-1)条边。一棵有n个顶点的生成树(连通无回路图)有且仅有(n-1)条边。
  • 最小生成树:对于一个带权(假设每条边上的权均为大于0的实数)连通无向图G中的不同生成树,其每棵树的所有边上的和也可能不同,图的所有生成树中具有边上的权值之和最小的树称为最小生成树。

例如:要在n个城市之间铺设光缆,主要目标是要使这 n 个城市的任意两个之间都可以通信,但铺设光缆的费用很高,且各个城市之间铺设光缆的费用不同,因此另一个目标是要使铺设光缆的总费用最低。这就需要找到带权的最小生成树。

这里介绍最小生成树两种方法:Prim和Kruskal。

  1. Prim算法

    假设V是图中顶点的集合,E是图中边的集合,TE为最小生成树中的边的集合,则prim算法通过以下步骤可以得到最小生成树:

    1:初始化:U={u 0},TE={f}。此步骤设立一个只有结点u 0的结点集U和一个空的边集TE作为最小生成树的初始形态,在随后的算法执行中,这个形态会不断的发生变化,直到得到最小生成树为止。

    2:在所有u∈U,v∈V-U的边(u,v)∈E中,找一条权最小的边(u 0,v 0),将此边加进集合TE中,并将此边的非U中顶点加入U中。此步骤的功能是在边集E中找一条边,要求这条边满足以下条件:首先边的两个顶点要分别在顶点集合U和V-U中,其次边的权要最小。找到这条边以后,把这条边放到边集TE中,并把这条边上不在U中的那个顶点加入到U中。这一步骤在算法中应执行多次,每执行一次,集合TE和U都将发生变化,分别增加一条边和一个顶点,因此,TE和U是两个动态的集合,这一点在理解算法时要密切注意。

    3:如果U=V,则算法结束;否则重复步骤2。可以把本步骤看成循环终止条件。我们可以算出当U=V时,步骤2共执行了n-1次(设n为图中顶点的数目),TE中也增加了n-1条边,这n-1条边就是需要求出的最小生成树的边。

  2. Kruskal算法:假设 WN=(V,{E}) 是一个含有 n 个顶点的连通网,则按照克鲁斯卡尔算法构造最小生成树的过程为:先构造一个只含 n 个顶点,而边集为空的子图,若将该子图中各个顶点看成是各棵树上的根结点,则它是一个含有 n 棵树的一个森林。之后,从网的边集 E 中选取一条权值最小的边,若该条边的两个顶点分属不同的树,则将其加入子图,也就是说,将这两个顶点分别所在的两棵树合成一棵树;反之,若该条边的两个顶点已落在同一棵树上,则不可取,而应该取下一条权值最小的边再试之。依次类推,直至森林中只有一棵树,也即子图中含有 n-1条边为止。

两者的区别:Prim是以更新过的节点的连边找最小值,Kruskal是直接将边排序;Prim在稠密图中比Kruskal优,在稀疏图中比Kruskal劣。两者都运用了贪心的思路。

下面推荐两个最小生成树的模板题,供大家参考。

题目传送门

题意:裸的求最小生成树,采用了Prim+优先队列AC。

#include<cstdio>
#include<queue>
#include<cstring>
#include<algorithm>
#define R register int
using namespace std;

int k,n,m,cnt,sum,head[1000],dis[1000],vis[1000];
int mp[110][110];
typedef pair <int,int> pii;
priority_queue <pii,vector<pii>,greater<pii> > q;
struct Edge
{
    int v,w,next;
}e[400005];
void add(int u,int v,int w)
{
    e[++k].v=v;
    e[k].w=w;
    e[k].next=head[u];
    head[u]=k;
}
void prim()
{
    dis[1]=0;
    q.push(make_pair(0,1));
    while(!q.empty()&&cnt<n)
    {
        int d=q.top().first,u=q.top().second;
        q.pop();
        if(vis[u]) continue;
        cnt++;
        sum+=d;
        vis[u]=1;
        for(R i=head[u];i!=-1;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));
    }
}
int main()
{
	int i,j;
    while(scanf("%d",&n)!=EOF)
    {
    	memset(dis,127,sizeof(dis));
    	memset(head,-1,sizeof(head));
    	for(i=1;i<=n;i++)
    	{
    		for(j=1;j<=n;j++)
    		{
    			scanf("%d",&mp[i][j]);
    			if(i!=j)
    			{
    				add(i,j,mp[i][j]);	
				}
			}
		}
    	prim();
    	if (cnt==n)
			printf("%d\n",sum);
	}
	return 0;
}

题目传送门2

题意:求最小生成树中最大的边的权值,采用Kruskal+并查集优化。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
using namespace std;
#define N 510
struct node{
    int x,y,v;
}e[N*N];
int t,fa[N];
bool cmp(const node &a,const node &b){
    return a.v<b.v;
}
int find(int x){
    return fa[x]==x?x:fa[x]=find(fa[x]);
}
int main(){
    scanf("%d",&t);
    while(t--){
        int n,cnt=0;
        scanf("%d",&n);
        for(int i=1;i<=n;i++) fa[i]=i;
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                int x;
                scanf("%d",&x);
                if(x){
                    e[++cnt].x=i;e[cnt].y=j;e[cnt].v=x;
                }
            }
        }
        int k=0,res=0;
        sort(e+1,e+cnt+1,cmp);
        for(int i=1;i<=cnt;i++){
            int fx=find(e[i].x),fy=find(e[i].y);
            if(fx!=fy){
                fa[fy]=fx;
                k++;
            }
            if(k==n-1){
                res=e[i].v;
                break;
            }
        }
        printf("%d\n",res);
    }
    return 0;
}

上面两个题为最小生成树的基础题,刚接触最小生成树可以拿这两个题目来练练手,祝大家早日AC。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值