最小生成树的概念
在一个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算法
手把手学会:并查集(见博客)
基本思路:把所有的边权按从小到大排序,每次选出边权最小的边,如果将要加入的边与目前所加入的边形成了环,则跳过该边,使用并查集判断环
#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;
}
本文介绍最小生成树的基本概念及两种核心算法:Prim算法和Kruskal算法。Prim算法通过贪心策略逐步构建生成树,适用于稠密图;而Kruskal算法则利用并查集判断环路,对边进行排序后逐一加入生成树。文章还提供了算法实现代码。

1万+

被折叠的 条评论
为什么被折叠?



