一、最小生成树的相关知识

1. 树的性质

树实际上是图的特殊形态,

对于任意一张无向连通图\(G=(V,E)\),其中\(n=|V|,m=|E|\)。

该图为树时的性质:

  1. |E|=|V|-1
  2. 图中无环
  3. 图连通
  4. 任意两点有且仅有一条简单路径
  5. 删除任意一条边后该图不连通

2. 生成树

在一个有\(|V|\)个点的无向连通图中,取其中\(|V|-1\)条边,并连接所有的顶点,所得到的子图为原图的生成树(Spanning Tree)。

3. 最小生成树

在一个带权无向连通图中,各边权和最小的一棵生成树即为原图的最小生成树(Minimum Spanning Tree,MST)。

4. 最小生成树的性质

1. 最小边原则:图中权值最小的边(唯一)一定在最小生成树上。

2.唯一性定理:对于一个图\(G\),如果图中边权都不相同,那么该图的最小生成树唯一(形态不一定唯一)。


二、Kruskal算法求最小生成树

求出一个图的最小生成树共有两种算法:Prim算法Kruskal算法

由于Prim算法的实用性较低,因此这里不介绍该算法。

1. 核心思想

Kruskal算法是一种贪心思想,将边权按权值由小到大排序,并从剩下的边集中选择权值最小且两个端点不在同一集合的边加入生成树中,重复该操作直到加入了\(n-1\)条边。

2. 具体流程

  1. 将边权由小到大排序。
  2. 建立并查集,每个点都构成一个集合。
  3. 扫描每一条边(x,y,z),若发现x,y在同一集合,那么舍弃这条边;否则累计z到答案中并合并x,y到同一集合。
  4. 重复操作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-NetP1111 修复公路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算法的更多相关文章

  1. 最小生成树的Kruskal算法实现

    最近在复习数据结构,所以想起了之前做的一个最小生成树算法.用Kruskal算法实现的,结合堆排序可以复习回顾数据结构.现在写出来与大家分享. 最小生成树算法思想:书上说的是在一给定的无向图G = (V ...

  2. 数据结构与算法--最小生成树之Kruskal算法

    数据结构与算法--最小生成树之Kruskal算法 上一节介绍了Prim算法,接着来看Kruskal算法. 我们知道Prim算法是从某个顶点开始,从现有树周围的所有邻边中选出权值最小的那条加入到MST中 ...

  3. 邻接矩阵c源码(构造邻接矩阵,深度优先遍历,广度优先遍历,最小生成树prim,kruskal算法)

    matrix.c #include <stdio.h> #include <stdlib.h> #include <stdbool.h> #include < ...

  4. HDU1875——畅通工程再续(最小生成树:Kruskal算法)

    畅通工程再续 Description相信大家都听说一个“百岛湖”的地方吧,百岛湖的居民生活在不同的小岛中,当他们想去其他的小岛时都要通过划小船来实现.现在政府决定大力发展百岛湖,发展首先要解决的问题当 ...

  5. 23最小生成树之Kruskal算法

    图的最优化问题:最小生成树.最短路径 典型的图应用问题 无向连通加权图的最小生成树 有向/无向加权图的最短路径 四个经典算法 Kruskal算法.Prim算法---------------最小生成树 ...

  6. 最小生成树的Kruskal算法

        库鲁斯卡尔(Kruskal)算法是一种按照连通网中边的权值递增的顺序构造最小生成树的方法.Kruskal算法的基本思想是:假设连通网G=(V,E),令最小生成树的初始状态为只有n个顶点而无边的 ...

  7. 算法学习记录-图——最小生成树之Kruskal算法

    之前的Prim算法是基于顶点查找的算法,而Kruskal则是从边入手. 通俗的讲:就是希望通过 边的权值大小 来寻找最小生成树.(所有的边称为边集合,最小生成树形成的过程中的顶点集合称为W) 选取边集 ...

  8. 图论之最小生成树之Kruskal算法

    Kruskal算法,又称作为加边法,是配合并查集实现的. 图示: 如图,这是一个带权值无向图我们要求它的最小生成树. 首先,我们发现在1的所有边上,连到3的边的边权值最小,所以加上这条边. 然后在3上 ...

  9. 【最小生成树之Kruskal算法】

    看完之后推荐再看一看[最小生成树之Prim算法]-C++ 定义:一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边.最小生成树可以用kr ...

  10. 【转载】最小生成树之Kruskal算法

    给定一个无向图,如果它任意两个顶点都联通并且是一棵树,那么我们就称之为生成树(Spanning Tree).如果是带权值的无向图,那么权值之和最小的生成树,我们就称之为最小生成树(MST, Minim ...

随机推荐

  1. nmap加载nse脚本在内网渗透中的使用-上

    转载自:https://mp.weixin.qq.com/s/zEgHxJEOfaiYVZYmg7NnXA? 大多数情况下,大家都认为nmap只是一个扫描工具,而不把当成是一个渗透工具.nmap集成了 ...

  2. SCF(SenparcCoreFramework) 系列教程(一):项目介绍及快速搭建

    2020年3月25日的“盛派周三分享”活动首次使用直播的方式与大家见面,共有 500 多人参与了活动,得到了众多开发者的好评,并强烈要求我分享 PPT,这点要求当然必须满足啦! 除此以外,还有许多开发 ...

  3. IDEA 快捷键大全及常用插件

    IDEA快捷键操作 颜色主题插件: **Material Theme UI Plugin ** 快捷键提醒: **Key Promoter X ** 查找Bug: QAPlig-FindBugs 热部 ...

  4. Java导出Excel文件

    /** * 导出 用get请求 * @param response * @param * @throws IOException */ @RequestMapping(value = "/d ...

  5. coding++:TimeUnit 使用

    TimeUnit是java.util.concurrent包下面的一个类,表示给定单元粒度的时间段 主要作用 时间颗粒度转换 延时 常用的颗粒度 TimeUnit.DAYS //天 TimeUnit. ...

  6. Day13 流程控制

    Linux中的流程控制语句 一.if语句 1.单分支if条件语句 格式:if [ 条件判断式 ] then 程序     fi 注意:1.在Linux中是以if开头,fi结尾.其他地方一般是{开头,} ...

  7. mui在vue_cli上使用

    在main.js里添加 import mui from './assets/js/mui.js' 如果不添加下面会显示mui is not defined 报错 Vue.prototype.mui = ...

  8. Mysql索引、explain执行计划

    1.索引的使用场景 哪些情况使用索引: 1.主键自动建立唯一索引 2.频繁作为查询条件的字段应该创建索引 where 3.多表关联查询中,关联字段应该创建索引on两边都要创建索引 select * f ...

  9. PTA | 1008 数组元素循环右移问题 (20分)

    一个数组A中存有N(N>0)个整数,在不允许使用另外数组的前提下,将每个整数循环向右移M(M>=0)个位置,即将A中的数据由(A0 A1--AN-1)变换为(AN-M -- AN-1 A0 ...

  10. 用最新的版本,蹦最野的迪~~~IDE写大数据程序避坑指南

    文章更新于:2020-04-05 注:本次实验使用的操作系统及各个程序版本号 类别 版本号 说明 操作系统 Ubuntu 16.04.6 LTS 代号 xenial jdk java version ...