最小生成树

本文介绍最小生成树的基本概念及两种核心算法:Prim算法和Kruskal算法。Prim算法通过贪心策略逐步构建生成树,适用于稠密图;而Kruskal算法则利用并查集判断环路,对边进行排序后逐一加入生成树。文章还提供了算法实现代码。

最小生成树的概念

在一个n个点的带权无向连通图中,各边权和最小的的生成树为最小生成树(n个点,n-1条边)

(自己写的,不妥之处请指正)

性质:图中最小的边一定在最小生成树上

prim算法

prim算法是一种基于贪心的算法,将一个无向连通图的所有节点分为两部分:

A中的点已经加入生成树中

B中的点在生成树之外,等待加入生成树

在算法结束之后所有节点都进入A

算法流程:

1.每次先从B中选择dis[]值最小的点

(从这里可以看出prim 算法的一个优化就是用堆维护dis[])

2.维护dis[i]表示B中的每一个点到A中任意一个点(也就是到最小生成树)的最短距离

时间复杂度O(V^2)

堆优化O(Vlog(V))

适用于稠密图(点很少,边较多)

例题:【模板】最小生成树 - 洛谷

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int oo=2e9+8;
const int N=2e5+5;
inline int read()
{
	int s=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){s=(s<<1)+(s<<3)+(ch^48);ch=getchar();}
	return s*f;
}
struct edge
{
	int v,w,nxt;
}e[N<<1];
int head[N],dis[N],cnt,n,m;
//head[i]表示以i为起点的最后输入那条边的编号(cnt值)
//nxt[i]表示以第i条边的起点为起点前一条输入边的编号(cnt)
//head[u]类似邻接表中的表头 ,表示以u为起点的第一条边的编号
//edge[i].to表示第i条边所指向的点
//edge[i].nxt表示第i条边的下一条边的编号 
//dis数组已用点到未用点的最短距离 
bool vis[N];
inline void add(int u,int v,int w)
{
	cnt++;
	e[cnt].v=v;
	e[cnt].w=w;
	e[cnt].nxt=head[u];//将新边的nxt指向原来表头的nxt 
	head[u]=cnt;
}
inline int mn(int x,int y)
{
	return x<y?x:y;
}
int tot,now=1;
int ans;
int prim()
{
	int i;
	for(i=2;i<=n;i++)
		dis[i]=oo;
	for(i=head[1];i;i=e[i].nxt)
		dis[e[i].v]=mn(dis[e[i].v],e[i].w);
	now=1; 
	vis[1]=1;
	while(tot<n-1)
	{
		tot++;
		int minn=oo;
		for(i=1;i<=n;i++)
		{
			//枚举每一个没有使用过的点
			//找到其中边权最小的点 
			if(!vis[i]&&minn>dis[i])
			{
				minn=dis[i];
				now=i;
			}
		}
		vis[now]=1;
		ans+=minn;
		//枚举now 的所有新边,更新dis数组 
		for(i=head[now];i;i=e[i].nxt)
		{
			int v=e[i].v;
			if(dis[v]>e[i].w&&!vis[v])
				dis[v]=e[i].w; 
		}
	}
	return ans;
}
int main()
{
	//freopen("p3366.in","r",stdin);
	//freopen("p3366.out","w",stdout);
	n=read();m=read();
	int i,uu,vv,ww;
	for(i=1;i<=m;i++)
	{
		uu=read();
		vv=read();
		ww=read();
		add(uu,vv,ww);
		add(vv,uu,ww);
	}
	int anss=prim(); 
	for(i=1;i<=n;i++)
		if(!vis[i])
		{
			printf("orz\n");
			return 0;
		}
	printf("%d\n",ans);
	return 0;
}

堆优化版本

#include<queue>
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
const int N=4e5+5;
const int oo=2e9;
struct edge
{
	int v;
	int w;
	int nxt;
}e[N];
struct node
{
	int u;//节点u 
	int d;//dis[]值d 
	bool operator <(const node &x) const
	{
		return d>x.d;
	}
};
inline int read()
{
	int s=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){s=(s<<1)+(s<<3)+(ch^48);ch=getchar();}
	return s*f;
}
int cnt;
int head[N];
bool vis[N];
inline void add(int u,int v,int w)
{
	cnt++;
	e[cnt].v=v;
	e[cnt].w=w;
	e[cnt].nxt=head[u];//将新边的nxt指向原来表头的nxt 
	head[u]=cnt;
}
int dis[N];
inline int mn(int x,int y)
{
	return x<y?x:y;
}
int main()
{
	freopen("p3366.in","r",stdin);
	freopen("p3366.out","w",stdout);
	int n,m;
	n=read();m=read();
	int i,uu,vv,ww;
	for(i=1;i<=m;i++)
	{
		uu=read();
		vv=read();
		ww=read();
		add(uu,vv,ww);
		add(vv,uu,ww);
	}	
	for(i=2;i<=n;i++)
		dis[i]=oo;
	for(i=head[1];i;i=e[i].nxt)
		dis[e[i].v]=mn(dis[e[i].v],e[i].w);
	int now=1; 
	priority_queue<node> q;//堆中放的是未加入的点(第一个点除外) 
	q.push((node){1,0});
	for(i=2;i<=n;i++)
		dis[i]=oo;
	int ans=0,tt=1;
	while(!q.empty())
	{
		node tmp=q.top();
		q.pop();
		int uu=tmp.u,dd=tmp.d;
		if(vis[uu])continue;
		vis[uu]=1;
		//if(dd!=dis[uu])continue;
		ans+=dd;
		tt++;
		for(i=head[uu];i;i=e[i].nxt)
		{
			if(!vis[e[i].v]&&dis[e[i].v]>e[i].w)
			{
				dis[e[i].v]=mn(dis[e[i].v],e[i].w);
				q.push((node){e[i].v,dis[e[i].v]});
			}
		}
	}
	if(tt<n)
	{
		printf("orz");return 0;
	}
	printf("%d\n",ans); 
	return 0;
}

 Kruskal算法

手把手学会:并查集(见博客)

CSDN

基本思路:把所有的边权按从小到大排序,每次选出边权最小的边,如果将要加入的边与目前所加入的边形成了环,则跳过该边,使用并查集判断环

#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
const int N=4e5+5;
int fa[N];
struct edge
{
	int u;
	int v;
	int w;
}e[N];
int find(int x)
{
	if(fa[x]==x)
		return x;
	return fa[x]=find(fa[x]);
} 
bool cmp(edge x,edge y)
{
	return x.w<y.w;
}
int main()
{
	freopen("p3366.in","r",stdin);
	freopen("p3366.out","w",stdout);
	int n,m,i;
	scanf("%d %d",&n,&m);
	//n个点,m条边 
	for(i=1;i<=n;i++) 
		fa[i]=i;
	//使用并查集判断是否存在环 
	int uu,vv,ww;
	for(i=1;i<=m;i++)
	{
		scanf("%d %d %d",&uu,&vv,&ww);
		e[i].u=uu;
		e[i].v=vv;
		e[i].w=ww;
	}
	sort(e+1,e+1+m,cmp);
	uu=0;vv=0;ww=0;
	int num=0,ans=0;
	for(i=1;i<=m;i++)
	{
		uu=e[i].u;vv=e[i].v;
		uu=find(uu);vv=find(vv);
		if(fa[uu]==fa[vv])continue;
		num++;
		ans+=e[i].w;
		fa[vv]=fa[uu];
		if(num==n-1)break;
	//	fa[find(vv)]=find(uu);
	}
	printf("%d\n",ans);
	return 0;
} 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值