[总结]最小生成树之Kruskal算法
一、最小生成树的相关知识
1. 树的性质
树实际上是图的特殊形态,
对于任意一张无向连通图\(G=(V,E)\),其中\(n=|V|,m=|E|\)。
该图为树时的性质:
- |E|=|V|-1
- 图中无环
- 图连通
- 任意两点有且仅有一条简单路径
- 删除任意一条边后该图不连通
2. 生成树
在一个有\(|V|\)个点的无向连通图中,取其中\(|V|-1\)条边,并连接所有的顶点,所得到的子图为原图的生成树(Spanning Tree)。
3. 最小生成树
在一个带权无向连通图中,各边权和最小的一棵生成树即为原图的最小生成树(Minimum Spanning Tree,MST)。
4. 最小生成树的性质
1. 最小边原则:图中权值最小的边(唯一)一定在最小生成树上。
2.唯一性定理:对于一个图\(G\),如果图中边权都不相同,那么该图的最小生成树唯一(形态不一定唯一)。
二、Kruskal算法求最小生成树
求出一个图的最小生成树共有两种算法:Prim算法,Kruskal算法。
由于Prim算法的实用性较低,因此这里不介绍该算法。
1. 核心思想
Kruskal算法是一种贪心思想,将边权按权值由小到大排序,并从剩下的边集中选择权值最小且两个端点不在同一集合的边加入生成树中,重复该操作直到加入了\(n-1\)条边。
2. 具体流程
- 将边权由小到大排序。
- 建立并查集,每个点都构成一个集合。
- 扫描每一条边(x,y,z),若发现x,y在同一集合,那么舍弃这条边;否则累计z到答案中并合并x,y到同一集合。
- 重复操作3直到生成树中包含n-1条边。若所有边扫描完毕且生成树的边数不到n-1,那么该图不存在最小生成树。
总时间复杂度为:\(O(mlogm+m\alpha (n))\),其中\(\alpha (n)\)是一次并查集的复杂度。
3. 图示
以下组图具体描述了Step:0中的一个图求出最小生成树的过程。


Step:1初始每个点都是一个集合。

Step:2当前最小边权为2,因此将点1,2合并到同一个集合。

Step:3当前最小边权为3,因此将点3,5合并到同一个集合。

Step:4当前最小边权为6,点3,4不在同一集合,因此将点3,4合并到同一个集合。

Step:5当前最小边权为7,由于此时点4,5已经在同一集合,因此将点3,4合并到同一个集合。

Step:6当前最小边权为8,此时点2,3不在同一集合,因此将点2,3合并到同一个集合。又由于此时(m==n-1),故算法结束并返回答案:Ans=19。
以上,求图中最小生成树的问题得以解决。
4. 代码实施
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
int n,m,ans,cnt,fa[500100];
struct node{
int u,v,w;
bool friend operator <(node a,node b){
return a.w<b.w;
}
}edge[500010];
inline int Find(int x){return fa[x]==x? x:fa[x]=Find(fa[x]);}
void Kruskal(){
sort(edge+1,edge+m+1);//边权排序
for(int i=1;i<=m;i++){
int fu=Find(edge[i].u);
int fv=Find(edge[i].v);
if(fu==fv) continue;//已经在同一集合,由于再加入这条边会形成环,因此不考虑这条边
cnt++;//累计生成树中边的条数
ans+=edge[i].w;//累计答案
fa[fu]=fv;//合并到同一集合
if(cnt==n-1) break;//已经得出最小生成树
}
if(cnt<n-1){//无法形成生成树
cnt=0;return;
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++) scanf("%d%d%d",&edge[i].u,&edge[i].v,&edge[i].w);
for(int i=1;i<=n;i++) fa[i]=i;//并查集初始化
Kruskal();
if(cnt) printf("%d",ans);
else printf("No Answer");
return 0;
}
三、例题
例1:P2212 [USACO14MAR]浇地Watering the Fields
同P1546 最短网络 Agri-Net,P1111 修复公路,P1195 口袋的天空
模板题,具体解释见代码。
Code:
#include<bits/stdc++.h>
#define N 2500
using namespace std;
int n,c,m,px[N],py[N];
int pre[N],ans,cnt;
struct node{
int u,v,dis;
bool operator <(const node &other) const{
return dis<other.dis;
}
}q[20000000];
inline int find(int x){
return pre[x]==x ? x:pre[x]=find(pre[x]);
}
inline void Kruskal(){
for(int i=1;i<=n;i++) pre[i]=i;
for(int i=1;i<=m;i++){
int fu=find(q[i].u);
int fv=find(q[i].v);
if(fu!=fv){
pre[fu]=fv;
ans+=q[i].dis;
cnt++;
}
if(cnt==n-1) break;
}
if(cnt==n-1) printf("%d",ans);
else printf("-1");
}
int main()
{
scanf("%d%d",&n,&c);
for(int i=1;i<=n;i++){
scanf("%d%d",&px[i],&py[i]);
for(int j=1;j<i;j++){
int dis=(px[i]-px[j])*(px[i]-px[j])+(py[i]-py[j])*(py[i]-py[j]);//计算费用
if(dis<c) continue;//费用小于C的水管不会被安装,因此需要不建边
q[++m].u=i,q[m].v=j,q[m].dis=dis;
}
}
sort(q+1,q+m+1);
Kruskal();
return 0;
}
例2:P1550 [USACO08OCT]打井Watering Hole
我们唯一需要解决的问题在于第一口井打在哪里。不妨设一个超级原点,并把每口井与该点相连,边权就是打井的费用。在新图上跑最小生成树,这样就能巧妙地解决问题。
同P1194 买礼物
Code:
#include<bits/stdc++.h>
using namespace std;
int cnt,pre[200000],num,ans,n;
struct node{
int u,v,w;
bool operator <(const node &other) const{
return w<other.w;
}
}e[200000];
inline void add_edge(int u,int v,int w){
cnt++;
e[cnt].u=u;e[cnt].v=v;e[cnt].w=w;
}
inline int Find(int x){
return x==pre[x]? x:pre[x]=Find(pre[x]);
}
void Kruskal(){
for(int i=1;i<=cnt;i++){
int fu=Find(e[i].u);
int fv=Find(e[i].v);
if(fu==fv) continue;
pre[fu]=fv;
num++;
ans+=e[i].w;
if(num==n) break;//由于包含了超级原点的这一条边,因此最后有n条边
}
}
int main()
{
int u,v,w;
scanf("%d",&n);
for(int i=1;i<=n;i++){//与超级原点建边
scanf("%d",&w);
add_edge(0,i,w);
}
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++){
scanf("%d",&w);
if(i==j) continue;
add_edge(i,j,w);
}
sort(e+1,e+cnt+1);
for(int i=0;i<=n;i++) pre[i]=i;
Kruskal();
printf("%d",ans);
return 0;
}
例3:P1547 Out of Hay
求最小生成树中最大的一条边,每次把边加入生成树中不断取最大值即可。
或者,根据其贪心思想,越晚加进来的边权值越大,因此只要让边权不断赋值给答案就能求出最大值。
例4:P1340 兽径管理
每次只增加一条边,因此用Kruskal算法十分方便。
正常排序后标记产生道路的时间,循环m次最小生成树求的答案。
Code:
#include<bits/stdc++.h>
using namespace std;
int pre[1000000],n,m,ans,num;
struct node
{
int u,v,w,p;
bool operator <(const node &other)const{
return w<other.w;
}
}e[1000000];
inline int Find(int x)
{
if(pre[x]==x) return x;
pre[x]=Find(pre[x]);
return pre[x];
}
void Kruskal(int p)
{
ans=0;num=0;
for(int i=1;i<=n;i++) pre[i]=i;
for(int i=1;i<=m;i++)
{
int fu=Find(e[i].u);
int fv=Find(e[i].v);
if(fu==fv||e[i].p>p) continue;
pre[fu]=fv;
ans+=e[i].w;
num++;
if(num==n-1) {
printf("%d\n",ans);break;
}
}
if(num<n-1){
printf("-1\n");
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
e[i].u=u;e[i].v=v;e[i].w=w;
e[i].p=i;
}
sort(e+1,e+m+1);
for(int i=1;i<=n;i++) pre[i]=i;
for(int i=1;i<=m;i++) Kruskal(i);
return 0;
}

[总结]最小生成树之Kruskal算法的更多相关文章
- 最小生成树的Kruskal算法实现
最近在复习数据结构,所以想起了之前做的一个最小生成树算法.用Kruskal算法实现的,结合堆排序可以复习回顾数据结构.现在写出来与大家分享. 最小生成树算法思想:书上说的是在一给定的无向图G = (V ...
- 数据结构与算法--最小生成树之Kruskal算法
数据结构与算法--最小生成树之Kruskal算法 上一节介绍了Prim算法,接着来看Kruskal算法. 我们知道Prim算法是从某个顶点开始,从现有树周围的所有邻边中选出权值最小的那条加入到MST中 ...
- 邻接矩阵c源码(构造邻接矩阵,深度优先遍历,广度优先遍历,最小生成树prim,kruskal算法)
matrix.c #include <stdio.h> #include <stdlib.h> #include <stdbool.h> #include < ...
- HDU1875——畅通工程再续(最小生成树:Kruskal算法)
畅通工程再续 Description相信大家都听说一个“百岛湖”的地方吧,百岛湖的居民生活在不同的小岛中,当他们想去其他的小岛时都要通过划小船来实现.现在政府决定大力发展百岛湖,发展首先要解决的问题当 ...
- 23最小生成树之Kruskal算法
图的最优化问题:最小生成树.最短路径 典型的图应用问题 无向连通加权图的最小生成树 有向/无向加权图的最短路径 四个经典算法 Kruskal算法.Prim算法---------------最小生成树 ...
- 最小生成树的Kruskal算法
库鲁斯卡尔(Kruskal)算法是一种按照连通网中边的权值递增的顺序构造最小生成树的方法.Kruskal算法的基本思想是:假设连通网G=(V,E),令最小生成树的初始状态为只有n个顶点而无边的 ...
- 算法学习记录-图——最小生成树之Kruskal算法
之前的Prim算法是基于顶点查找的算法,而Kruskal则是从边入手. 通俗的讲:就是希望通过 边的权值大小 来寻找最小生成树.(所有的边称为边集合,最小生成树形成的过程中的顶点集合称为W) 选取边集 ...
- 图论之最小生成树之Kruskal算法
Kruskal算法,又称作为加边法,是配合并查集实现的. 图示: 如图,这是一个带权值无向图我们要求它的最小生成树. 首先,我们发现在1的所有边上,连到3的边的边权值最小,所以加上这条边. 然后在3上 ...
- 【最小生成树之Kruskal算法】
看完之后推荐再看一看[最小生成树之Prim算法]-C++ 定义:一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边.最小生成树可以用kr ...
- 【转载】最小生成树之Kruskal算法
给定一个无向图,如果它任意两个顶点都联通并且是一棵树,那么我们就称之为生成树(Spanning Tree).如果是带权值的无向图,那么权值之和最小的生成树,我们就称之为最小生成树(MST, Minim ...
随机推荐
- 面试官再问我如何保证 RocketMQ 不丢失消息,这回我笑了!
最近看了 @JavaGuide 发布的一篇『面试官问我如何保证Kafka不丢失消息?我哭了!』,这篇文章承接这个主题,来聊聊如何保证 RocketMQ 不丢失消息. 0x00. 消息的发送流程 一条消 ...
- 在eclipse里面给maven项目打包
eclipse中的“maven install”是用maven打包工程的意思. mvn install 是将用户打包好的jar包安装到本地仓库中,一般没有设置过的话默认在用户目录下的 .m2\下面. ...
- 从数据结构分析mysql为何使用B+tree
理解mysql为何选择升级版的二叉树,就需要对各种常用的二叉树进行对比.B+Tree是一种特殊的二叉树,本质上也算二叉树.自然会满足二叉树的一般特性. 比如,比节点数据大的在右边,节点数据小的在左边. ...
- 教你如何利用threejs对3D模型皮肤进行DIY
一步一步教你如何利用threejs加载gltf模型来实现DIY换肤功能. 模型准备 模型制作 模型可以通过网上下载,也可以自己通过c4d.maya.blender等模型制作软件得到.这里就不叙述有关模 ...
- Manjaro更新后 搜狗拼音输入法突然无法正常使用
之前Manjaro已经用了很久了,很多该配置的都已经配置好了,但是搜狗拼音在系统更新后突然无法使用 1检查 如下依赖 2.检查配置文件 3.发现一切配置没问题,此时输入 sogou-qimpanel ...
- 爬虫&Selenium&ChromeDriver
一.Selenium selenium是什么 Selenium [1] 是一个用于Web应用程序测试的工具.Selenium测试直接运行在浏览器中,就像真正的用户在操作一样.支持的浏览器包括IE(7, ...
- IO多路复用(IO Multiplexing)
什么是IO多路复用 为什么要有IO多路复用 作者总结 遵循学习新知识的三部曲:是什么?为什么?怎么用? 作者前言:IO多路复用本质上是网络通信过程中的一个技术名词. 什么是IO多路复用 一个用机场管理 ...
- OpenCV-Python 直方图-4:直方图反投影 | 二十九
目标 在本章中,我们将学习直方图反投影. 理论 这是由Michael J. Swain和Dana H. Ballard在他们的论文<通过颜色直方图索引>中提出的. 用简单的话说是什么意思? ...
- POJ - 3255 SPFA+邻接表求次短路径
题意:给出m条边 , n个顶点,u [ i ]到v [ i ] 的距离w [ i ],求除了最短路的那条最短的边的长度. 思路:之前有做过相似的题,使用迪杰斯特拉算法求单源最短路径,并且记录路径,枚举 ...
- coding++:mybatis update foreach (SQL循环)批量更新
今天要做批量更新的业务,采用 mybaits 的 foreach 动态语句,遇到一些问题做下记录. 参考示例(1): <update id="" parameterType= ...