什么是最小生成树?
Prim算法
就是先从任意一点开始,接下来找与其距离最小的选进去。每次选择已有点最接近距离的点并且标记。
时间复杂度O(n^2)
#include<bits/stdc++.h>
#define IO ios::sync_with_stdio(false);cin.tie(0);
using namespace std;
const int maxn= 5050;
const int inf = 0x3f3f3f3f;
typedef long long ll;
ll g[maxn][maxn];
ll mst[maxn];
ll lowcost[maxn];
ll n,m;
int prim(int x) //从x点开始扩展
{
int sum=0; //边权和
for(int i=1;i<=n;i++)
{
lowcost[i]=g[x][i]; //lowcost[i]表示以i为终点的边中最小的权值,等于-1 表示已在集合U中
//mst[i]=x; //记录路径的话 开个mst数组 mst[i]=x;表示当前集合U中到点的距离最小的点为x 即边(x,i)为候选边
}
lowcost[x]=-1;
for(int i=1;i<=n-1;i++)
{
int mind=inf,minid=0;
for(int j=1;j<=n;j++)
{
if(lowcost[j]<mind&&lowcost[j]!=-1)
{
mind=lowcost[j];//选出最小值(要加入最小生成树的边的边权)
minid=j;//记录要加入的点
}
}
sum+=mind;
lowcost[minid]=-1;
for(int i=1;i<=n;i++)
{
if(lowcost[i]>g[minid][i]) //更新候选值
{
lowcost[i]=g[minid][i];
}
}
}
return sum; //返回最小生成树边权值和
}
int main()
{
IO;
scanf("%d %d",&n,&m); //n个点,m条边
for(int i=1; i<=n; i++) //赋初值
{
for(int j=1; j<=n; j++)
g[i][j]=inf;
}
int u,v,w;
for(int i=0;i<m;i++)
{
scanf("%d %d %d",&u,&v,&w);
if(w<=g[u][v]) g[u][v]=g[v][u]=w;//去重边
}
int sum=prim(1);
if(sum<inf) printf("%d\n",sum);
else cout<<"orz"<<endl;
}
Kruskal算法
前置技能:并查集算法
#include<bits/stdc++.h>
using namespace std;
struct Edge
{
int u,v,w;
}edge[200005];
int fa[5005],n,m,ans,eu,ev,cnt;
inline bool cmp(Edge a,Edge b)
{
return a.w<b.w;
}
inline int find(int x)
{
while(x!=fa[x]) x=fa[x]=fa[fa[x]];
return x;
}
inline void kruskal()
{
sort(edge,edge+m,cmp);
for(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;
if(++cnt==n-1)
{
break;
}
}
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
fa[i]=i;
}
for(int i=0;i<m;i++)
{
cin>>edge[i].u;
cin>>edge[i].v;
cin>>edge[i].w;
}
kruskal();
printf("%d",ans);
return 0;
}
Prim和Kruskal的区别:
Prim算法是依赖于点的算法。它的基本原理是从当前点寻找一个离自己(集合)最近的点然后把这个点拉到自己家来(距离设为0),同时输出一条边,并且刷新到其他点的路径长度。俗称,刷表。
根据Prim算法的特性可以得知,它很适合于点密集的图。通常在教材中,对Prim算法进行介绍的标程都采用了邻接矩阵的储存结构。这种储存方法空间复杂度N2,时间复杂度N2。对于稍微稀疏一点的图,其实我们更适合采用邻接表的储存方式,可以节省空间,并在一定条件下节省时间。
Kruskal算法是依赖边的算法。基本原理是将边集数组排序,然后通过维护一个并查集来分清楚并进来的点和没并进来的点,依次按从小到大的顺序遍历边集数组,如果这条边对应的两个顶点不在一个集合内,就输出这条边,并合并这两个点。
根据Kruskal算法的特性可得,在边越少的情况下,Kruskal算法相对Prim算法的优势就越大。同时,因为边集数组便于维护,所以Kruskal在数据维护方面也较为简单,不像邻接表那样复杂。从一定意义上来说,Kruskal算法的速度与点数无关,因此对于稀疏图可以采用Kruskal算法。
关于去重问题的探讨:
Prim算法的写法需要去重,去重方法就是记录比较最小值
Kruskal算法不需要特意去重,因为我执行m-1次操作