最小生成树
部分内容参考自网络。
基础概念
-
最小生成树的定义
生成树:一个无向图
G 的生成树属于G 的一个极小连通子图,包含该图的所有顶点S ,且只含|S|-1 条边,也就是满足树的性质。最小生成树:边权和最小的生成树。
-
瓶颈生成树的定义
-
最小生成树的性质
如有不严谨的地方,还请各位提出。
-
删去任意一条边后不再连通,加入任意一条边后出现环。
这一点很重要,在修改最小生成树时有重要作用。
-
所有的边权和尽可能小(对于全图而言)。
-
-
最小生成树的树形不唯一,但边权和唯一。
-
最小生成树一定是瓶颈生成树,但瓶颈生成树不一定是最小生成树。
对于前一条,考虑反证法:设最小生成树的最大边权为
w 。若最小生成树不是瓶颈生成树,则瓶颈生成树的最大边权一定小于w 。那么只需删去w ,将瓶颈生成树中选取一条边加入最小生成树,使得删除后不连通的两部分连通,则可以得到一棵比原生成树更小的生成树。这与定义矛盾。运用这个性质,求瓶颈生成树时直接跑一遍最小生成树即可。
-
两种算法
-
Prim
-
简介
时间复杂度为
O(n^2) ,由于其需要搭配邻接矩阵适用,因此空间复杂度为O(n^2) 。本质上是贪心的思想,与
\operatorname{Dijkstra} 的连点法类似。将图分为两部分,一部分为生成树的点集
V ,另一部分为非生成树的点集S 。 -
算法流程
定义
dis[u] 为u 至V 的最小距离。- 初始化
dis 为无穷大,随便令一点的dis 为 0。 - 每次从
S 中找到最小dis 值dis[id] ,将id 加入V 中,同时将ans 增加dis[id] 。 - 通过
id 更新S 中点的dis 值。 - 重复
1 ~3 步n 次。 - 若在执行
2 时,发现找不到id ,则说明不存在最小生成树。否则输出ans 即可。
- 初始化
-
代码实现
inline void prim(){ dis[1]=0; for(int i=1; i<=n;++i){ id=0; for(int j=1; j<=n;++j){ if(!f[j]&&(!id||dis[j]<dis[id])){ id=j; } } if(dis[id]==0x3f3f3f3f){ flag=1; return ; } f[id]=1; ans+=dis[id]; for(int j=1; j<=n;++j){ if(!f[j]){ dis[j]=min(dis[j],a[id][j]); } } } }
-
-
Kruskal
-
简介
时间复杂度为
O(mlogm) ,利用邻接表存边,空间复杂度为O(m) (注意无向图要存两次)。将图视作若干个生成树(一个点也算)。每次合并都要使合并后得到的生成树尽可能小,那么最后便可得到最小生成树。
因此从大到小对边进行排序,依次取边合并两端点,即可满足上述条件。
-
算法流程
- 将边从大到小排序。
- 依次取边,若加入
e(u,v) 后不会产生环,则取e(u,v) 作为最小生成树中的一条边,并将ans 加上w(u,v) 。 - 若取不到
n-1 条边,则说明不存在最小生成树。否则输出ans 即可。
-
代码实现
判断加入后是否产生环,考虑使用并查集解决。
inline void kruskal(){ sort(e+1,e+1+cnt,cmp); for(int i=1; i<=cnt;i++){ int fu,fv,w; fu=find(e[i].u); fv=find(e[i].v); w=e[i].w; if(fu^fv){ fa[fu]=fv; tot++; ans+=w; } } }
-