最小生成树是无向图中的算法,最基础的是在一张无向图中求一棵树,该树包含n个点,同时树上所有边的边权和最小。
性质:
包含n个点,n-1条边,
任意两点之间都是连通的,
边权和最小,
可能不止一个,
但是边权和是固定的。
有两种算法可以实现:Prim(普利姆算法)和kruskal(克鲁斯卡尔算法)
Prim
时间复杂度:O(n^2)
Prim算法的本质是从任意一点开始往外扩展,将点放进集合中,同时每次循环找到不在集合中且距离集合最近的点(这里也是它和dijkstra算法的区别,dijkstra算法每次循环找的是距离起点最近的点),把它加进集合,统计将这条边计入最短路,然后再用它去更新其他的点,当所有点都被加进集合的时候,那么就找到了一棵最小生成树。
int g[][],st[][];
int d[];
int res;
void prim()
{
memset(d,0x3f,sizeof d);
d[1]=0;//这里其实无所谓,从任意一点开始都可以
for(int i=1;i<=n;i++)
{
int t=-1;
for(int j=1;j<=n;j++)
if(!st[j]&&(t==-1||d[j]<d[t])) t=j;
st[t]=1;
res += d[t];
for(int i=1;i<=n;i++)
if(d[i]>g[t][i])//t已经放入集合中了,所以g[t][i]才是距离集合的距离
d[i]=g[t][i];
}
}
ps:乍一看真的和朴素版dijkstra算法很像,唯一的区别就在距离的更新上
kruskal
时间复杂度:O(mlogm)
这个算法的思路就是将所有的边排序,然后从小到大遍历所有的边,如果这条边连接的两点不在一个集合中,那么就将它们并成一个集合,否则就略过这条边。所以这里的时间复杂度是sort排序的时间,实际上如果m不大的话,可以视为O(m)。另外这种算法比较有趣的一点在于边可以用任意方式存,最简单的就是直接用结构体来存。
struct edge{
int a,b,c;
}e[];
bool cmp(edge x,edge y)
{
return x.c<y.c;
}
int p[];
int find(int x)
{
if(p[x]!=x) p[x]=find(p[x]);
return p[x];
}
int main()
{
...
sort(e+1,e+m+1);
int res=0;
for(int i=1;i<=m;i++)
{
int a=e[i].a,b=e[i].b,c=e[i].c;
a=find(a),b=find(b);//此时a变成a的祖先节点,b变成b的祖先节点
if(a!=b) p[a]=b,res+=c;//直接将两个并查集的祖先节点合到一块儿
}
...
}
一般来说,可以用prim算法的都可以用kluskal算法,但能用kluskal算法的却未必能用prim算法。
1140. 最短网络(活动 - AcWing)
这题其实很裸,连接所有点,边权和最小,那么就是求最小生成树。这题给的是邻接矩阵,所以prim算法要更方便一些。
#include<bits/stdc++.h>
using namespace std;
int g[120][120],n,res,d[120],st[120];
void prim()
{
memset(d,0x3f,sizeof d);
d[1]=0;
for(int i=1;i<=n;i++)
{
int t=-1;
for(int j=1;j<=n;j++)
if(!st[j]&&(t==-1||d[t]>d[j]))t=j;
st[t]=1;
res += d[t];
for(int j=1;j<=n;j++)
if(d[j]>g[t][j])
d[j]=g[t][j];
}
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
scanf("%d",&g[i][j]);
prim();
cout<<res;
}
1141. 局域网(活动 - AcWing)
思路:边都是无向边,我们要去除一些网线使得网络中没有回路且不影响连通性,不影响连通性那么就是原本连通的去完还是连通的,然后还要没有回路,那么就是树,但是要注意一点,原本各个点未必都是连通的,所以我们最后得到的相当于是一个森林。然后去除的最大,那么留下的就是最小的,我们可以形象的称之为