今天复习了最小生成树算法,最小生成树算法分为kruskal和prim两种。由于kruskal需要并查集实现。先给出几个并查集的基本操作:
1.并查集的初始化
for(int i=1;i<=n;i++)
father[i]=i;//先让每一个元素自己为一个集合
2.查找x所属集合
int findfather(int x)
{
if(father[x]!=x)
father[x]=findfather(father[x]);
return father[x];
}
3.将y合并到x的集合
void join(int x,int y)
{
int fx=findfather(x);
int fy=findfather(y);
if(fx!=fy)
father[fx]=fy;
}
步入正题
prim
个人觉得Prim和最短路中的dijkstra很像,由于速度问题,所以这里我用边表存图。Prim的思想是将任意节点作为根,再找出与之相邻的所有边(用一遍循环即可),再将新节点更新并以此节点作为根继续搜,维护一个数组:dis,作用为已用点到未用点的最短距离。
证明:Prim算法之所以是正确的,主要基于一个判断:对于任意一个顶点v,连接到该顶点的所有边中的一条最短边(v, vj)必然属于最小生成树(即任意一个属于最小生成树的连通子图,从外部连接到该连通子图的所有边中的一条最短边必然属于最小生成树)
代码实现
#include<bits/stdc++.h>
#define inf 0x7fffffff
int nxt[500010];
int head[500010];
int to[500010];
int edge[500010];
int vis[500010];
int t=1;
int cnt,n,m;
int dis[500010];
int minn;
int ans;
int now;
using namespace std;
inline void add(int x,int y,int u)
{
cnt++;
nxt[cnt]=head[x];
head[x]=cnt;
to[cnt]=y;
edge[cnt]=u;
}
inline void prim()
{
for(int i=2;i<=n;i++)
{
dis[i]=inf;
}
vis[1]=1;
for(int i=head[1];i;i=nxt[i])
{
dis[to[i]]=min(dis[to[i]],edge[i]);
}
now=1;
while(t<n)
{
t++;
minn=inf;
vis[now]=1;
for(int i=1;i<=n;i++)
{
if(dis[i]<minn&&!vis[i])
{
now=i;
minn=dis[i];
}
}
vis[now]=1;
ans+=minn;
for(int i=head[now];i;i=nxt[i])
{
if(!vis[to[i]]&&dis[to[i]]>edge[i])
{
dis[to[i]]=edge[i];
}
}
}
}
int main()
{
memset(edge,0,sizeof edge);
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
add(x,y,z);
add(y,x,z);
}
prim();
printf("%d",ans);
return 0;
}
Kruskal
Kruskal算法的思想比Prin好理解一些。先把边按照权值进行排序,用贪心的思想优先选取权值较小的边,并依次连接,若出现环则跳过此边(用并查集来判断是否存在环)继续搜,直到已经使用的边的数量比总点数少一即可。
证明:刚刚有提到:如果某个连通图属于最小生成树,那么所有从外部连接到该连通图的边中的一条最短的边必然属于最小生成树。所以不难发现,当最小生成树被拆分成彼此独立的若干个连通分量的时候,所有能够连接任意两个连通分量的边中的一条最短边必然属于最小生成树
代码实现
#include<bits/stdc++.h>
using namespace std;
#define re register
#define il inline
il int read()
{
re int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-') f=-1;c=getchar();}
while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
return x*f;
}
struct Edge
{
int u,v,w;
}edge[200005];
int fa[5005],n,m,ans,eu,ev,cnt;
il bool cmp(Edge a,Edge b)
{
return a.w<b.w;
}
il int find(int x)
{
while(x!=fa[x]) x=fa[x]=fa[fa[x]];
return x;
}
il void kruskal()
{
sort(edge,edge+m,cmp);
//将边的权值排序
for(re int i=0;i<m;i++)
{
eu=find(edge[i].u), ev=find(edge[i].v);
if(eu==ev)
{
continue;
}
//若出现两个点已经联通了,则说明这一条边不需要了
ans+=edge[i].w;
//将此边权计入答案
fa[ev]=eu;
//将eu、ev合并
if(++cnt==n-1)
{
break;
}
//循环结束条件,及边数为点数减一时
}
}
int main()
{
n=read(),m=read();
for(re int i=1;i<=n;i++)
{
fa[i]=i;
}
for(re int i=0;i<m;i++)
{
edge[i].u=read(),edge[i].v=read(),edge[i].w=read();
}
kruskal();
printf("%d",ans);
return 0;
}