[总结]最小生成树之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 ...
随机推荐
- shell脚本基础-四种启动方式
1.当前路径启动 ./test.sh 2.绝对路径启动 pwd /data/beijing 'pwd'/test.sh 3.指定解释器执行 sh test.sh bash test.sh 4.shel ...
- 2020kali浏览器汉化等配置
0.修改搜索引擎 1. 2. 3.点击左侧搜索,输入language因为我已经修改为中文所以没有查询到结果 4点击搜索更多语言(未汉化未英文)找到chinese后添加 5.要将chinese上移到第一 ...
- 《大空头》与A股内幕消息
目录 <大空头>简介 投行人士透露内幕消息不划算 <大空头>里合规性的一些解释 相信A股内幕消息的一些惨痛教训. 风险提示. <大空头>简介 <大空头> ...
- 贪心-谷歌-857. 雇佣 K 名工人的最低成本
2020-03-15 22:00:39 问题描述: 有 N 名工人. 第 i 名工人的工作质量为 quality[i] ,其最低期望工资为 wage[i] . 现在我们想雇佣 K 名工人组成一个工资组 ...
- 贪心-Bag of Tokens
2020-01-20 22:32:28 问题描述: 问题求解: 双指针 + 贪心. public int bagOfTokensScore(int[] tokens, int P) { Arrays. ...
- Django HttpResponse
HttpResponse 概述:给浏览器返回数据 HttpRequest对象是由django创建的,HttpResponse对象由程序员创建 用法 1:不调用模板,直接返回数据. 例: def get ...
- 动态规划(Dynamic Programming)算法与LC实例的理解
动态规划(Dynamic Programming)算法与LC实例的理解 希望通过写下来自己学习历程的方式帮助自己加深对知识的理解,也帮助其他人更好地学习,少走弯路.也欢迎大家来给我的Github的Le ...
- Python第十一章-常用的核心模块03-json模块
python 自称 "Batteries included"(自带电池, 自备干粮?), 就是因为他提供了很多内置的模块, 使用这些模块无需安装和配置即可使用. 本章主要介绍 py ...
- windows server 2016 远程桌面mstsc DPI(更改文本、应用和其他项目大小) 设置
windows server 2016 远程桌面mstsc DPI 设置 在高分辨率机器2K,4K,8K,登入使用window远程桌面mstsc时,登入后虽然分辨率变成了和cilent一样分辨率 但是 ...
- CtenOS开放3306端口
1.查看防火墙状态 2. 关闭防火墙firewall 3. 开启端口 4. 重启防火墙 5. 常用命令介绍 在 Centos 7 中防火墙由 firewalld 来管理,而不是 iptables. 1 ...