最小生成树(Prim、Kruskal)算法,秒懂!
前言
在数据结构与算法的图论中,(生成)最小生成树算法是一种常用并且和生活贴切比较近的一种算法。但是可能很多人对概念不是很清楚,什么是最小生成树?
一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边。 最小生成树可以用kruskal(克鲁斯卡尔)算法或prim(普里姆)算法求出。
通俗易懂的讲就是最小生成树包含原图的所有节点而只用最少的边
和最小的权值距离
。因为n
个节点最少需要n-1
个边联通,而距离就需要采取某种策略选择恰当的边。
学习最小生成树实现算法之前我们要先高清最小生成树的结构和意义所在。咱么首先根据一些图更好的祝你理解。
一个故事
在城市道路规划中,是一门很需要科学的研究(只是假设学习不必当真)。在公路时代城市联通的主要矛盾是时间慢,而造价相比运输时间是次要矛盾。所以在公路时代我们尽量使得城市能够直接联通,缩短城市联系时间,而稍微考虑建路成本!随着科技发展、高级铁路、信息传输相比公路运输快非常非常多,从而事件的主要矛盾从运输时间转变为造价成本,故有时会关注联通所有点的路程(最短),这就用到最小生成树算法。
城市道路铺设可能经历以下几个阶段。
- 初始,各个城市没有高速公路(铁路)。
- 政府打算各个城市铺设公路(铁路),每个城市都想成为交通枢纽,快速到达其他城市,但每个城市都有这种想法,如果实现下去造价太昂贵。并且造成巨大浪费。
- 最终国家选择一些主要城市进行联通,有个别城市只能稍微绕道而行,而绕道太远的、人流量多的国家考虑新建公路(铁路),适当提高效率。
不过上面铁路规划上由于庞大的人口可能不能够满足与"有铁路"这个需求,人们对速度、距离、直达等条件一直在追求中……
但是你可以想象这个场景:有些东西造假非常非常昂贵,使用效率非常高,我这里假设成黄金镶钻电缆 铺设,所以各个城市只要求不给自己落下,能通上就行(没人敢跳了吧)。
要从有环图中选取代价和最小的路线一方面代价最小(总距离最小最省黄金)另一方面联通所有城市。
然而根据上图我们可以得到以下最小生成树,但是最么生成这个最小生成树,就是下面要讲的了。
而类似的还有局部区域岛屿联通修桥,海底通道这些高成本的都多多少少会运用。
Kruskal算法
上面介绍了最小生成树是什么,现在需要掌握和理解最小生成树如何形成。给你一个图,用一个规则生成一个最小生成树。而在实现最小生成树方面有prim和kruskal算法,这两种算法的策略有所区别,但是时间复杂度一致。
Kruskal算法,和前面讲到的并查集关系很大,它的主要思想为:
先构造一个只含 n 个顶点、而边集为空的子图,把子图中各个顶点看成各棵树上的根结点,之后,从网的边集 E 中选取一条权值最小的边,若该条边的两个顶点分属不同的树,则将其加入子图,即把两棵树合成一棵树,反之,若该条边的两个顶点已落在同一棵树上,则不可取,而应该取下一条权值最小的边再试之。依次类推,直到森林中只有一棵树,也即子图中含有 n-1 条边为止。
简而言之,Kruskal
算法进行调度的单位是边,它的信仰为:所有边能小则小,算法的实现方面要用到并查集判断两点是否在同一集合。
而算法的具体步骤为:
- 将图中所有边对象(边长、两端点)依次加入集合(优先队列)
q1
中。初始所有点相互独立。 - 取出集合(优先队列)
q1
最小边,判断边的两点是否联通。 - 如果联通说明两个点已经有其它边将两点联通了,跳过,如果不连通,则使用
union
(并查集合并)将两个顶点合并,这条边被使用(可以储存或者计算数值)。 - 重复2,3操作直到集合(优先队列)
q1
为空。此时被选择的边构成最小生成树。
Prim算法
除了Kruskal
算法以外,普里姆算法(Prim
算法)也是常用的最小生成树算法。虽然在效率上差不多。但是贪心的方式和Kruskal
完全不同。
prim算法的核心信仰是:从已知扩散寻找最小。它的实现方式和Dijkstra
算法相似但稍微有所区别,Dijkstra是求单源最短路径,而每计算一个点需要对这个点重新更新距离,而prim不用更新距离。直接找已知点的邻边最小加入即可!prim
和kruskal
算法都是从边入手处理。
对于具体算法具体步骤,大致为:
- 寻找图中任意点,以它为起点,它的所有边V加入集合(优先队列)
q1
,设置一个boolean数组bool[]
标记该位置(边有两个点,每次加入没有被标记那个点的所有边)。 - 从集合q1找到距离最小的那个边
v1
并 判断边是否存在未被标记的一点p
,如果p
不存在说明已经确定过那么跳过当前边处理,如果未被标(访问)记那么标记该点p
,并且与p相连的未知点(未被标记)构成的边加入集合q1
, 边v1(可以进行计算距离之类,该边构成最小生成树) . - 重复1,2直到q1为空,构成最小生成树 !
大体步骤图解为:
因为prim从开始到结束一直是一个整体在扩散,所以不需要考虑两棵树合并的问题,在这一点实现上稍微方便了一点。
当然,要注意的是最小生成树并不唯一,甚至同一种算法生成的最小生成树都可能有所不同,但是相同的是无论生成怎样的最小生成树:
- 能够保证所有节点连通(能够满足要求和条件)
- 能够保证所有路径之和最小(结果和目的相同)
- 最小生成树不唯一,可能多样的
代码实现
上面分析了逻辑实现。下面我们用代码简单实现上述的算法。
prim
package 图论;import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.PriorityQueue;
import java.util.Queue;public class prim {public static void main(String[] args) {int minlength=0;//最小生成树的最短路径长度int max=66666;String cityname[]= {"北京","武汉","南京","上海","杭州","广州","深圳"};int city[][]= {{ max, 8, 7, max, max, max, max }, //北京和武汉南京联通{ 8, max,6, max,9, 8,max }, //武汉——北京、南京、杭州、广州{ 7, 6, max, 3,4, max,max }, //南京——北京、武汉、上海、杭州{ max, max,3, max,2, max,max }, //上海——南京、杭州{ max, 9,4, 2,max, max,10 }, //杭州——武汉、南京、上海、深圳{ max, 8,max, max,max, max,2 }, //广州——武汉、深圳{ max, max,max, max,10,2,max }//深圳——杭州、广州};// 地图boolean istrue[]=new boolean[7];//南京Queue<side>q1=new PriorityQueue<side>(new Comparator<side>() {public int compare(side o1, side o2) {// TODO Auto-generated method stubreturn o1.lenth-o2.lenth;}});for(int i=0;i<7;i++){if(city[2][i]!=max){istrue[2]=true;q1.add(new side(city[2][i], 2, i));}} while(!q1.isEmpty()){side newside=q1.poll();//抛出if(istrue[newside.point1]&&istrue[newside.point2]){continue;}else {if(!istrue[newside.point1]){istrue[newside.point1]=true;minlength+=city[newside.point1][newside.point2];System.out.println(cityname[newside.point1]+" "+cityname[newside.point2]+" 联通");for(int i=0;i<7;i++){if(!istrue[i]){q1.add(new side(city[newside.point1][i],newside.point1,i));}}}else {istrue[newside.point2]=true;minlength+=city[newside.point1][newside.point2];System.out.println(cityname[newside.point2]+" "+cityname[newside.point1]+" 联通");for(int i=0;i<7;i++){if(!istrue[i]){q1.add(new side(city[newside.point2][i],newside.point2,i));}}}}}System.out.println(minlength); }static class side//边{int lenth;int point1;int point2;public side(int lenth,int p1,int p2) {this.lenth=lenth;this.point1=p1;this.point2=p2;}}}
输出结果:
上海 南京 联通
杭州 上海 联通
武汉 南京 联通
北京 南京 联通
广州 武汉 联通
深圳 广州 联通
28
Kruskal:
package 图论;import java.util.Comparator;
import java.util.PriorityQueue;
import java.util.Queue;import 图论.prim.side;
/** 作者:bigsai(公众号)*/
public class kruskal {static int tree[]=new int[10];//bing查集public static void init() {for(int i=0;i<10;i++)//初始{tree[i]=-1;}}public static int search(int a)//返回头节点的数值{if(tree[a]>0)//说明是子节点{return tree[a]=search(tree[a]);//路径压缩}elsereturn a;}public static void union(int a,int b)//表示 a,b所在的树合并小树合并大树(不重要){int a1=search(a);//a根int b1=search(b);//b根if(a1==b1) {//System.out.println(a+"和"+b+"已经在一棵树上");}else {if(tree[a1]<tree[b1])//这个是负数,为了简单减少计算,不在调用value函数{tree[a1]+=tree[b1];//个数相加 注意是负数相加tree[b1]=a1; //b树成为a的子树,直接指向a;}else{tree[b1]+=tree[a1];//个数相加 注意是负数相加tree[a1]=b1; //b树成为a的子树,直接指向a;}}}public static void main(String[] args) {// TODO Auto-generated method stubinit();int minlength=0;//最小生成树的最短路径长度int max=66666;String cityname[]= {"北京","武汉","南京","上海","杭州","广州","深圳"};boolean jud[][]=new boolean[7][7];//加入边需要防止重复 比如 ba和ab等价的int city[][]= {{ max, 8, 7, max, max, max, max }, { 8, max,6, max,9, 8,max }, { 7, 6, max, 3,4, max,max }, { max, max,3, max,2, max,max }, { max, 9,4, 2,max, max,10 }, { max, 8,max, max,max, max,2 }, { max, max,max, max,10,2,max }};// 地图boolean istrue[]=new boolean[7];//南京Queue<side>q1=new PriorityQueue<side>(new Comparator<side>() {//优先队列存边+public int compare(side o1, side o2) {// TODO Auto-generated method stubreturn o1.lenth-o2.lenth;}});for(int i=0;i<7;i++){for(int j=0;j<7;j++){if(!jud[i][j]&&city[i][j]!=max)//是否加入队列{jud[i][j]=true;jud[j][i]=true;q1.add(new side(city[i][j], i, j));}}}while(!q1.isEmpty())//执行算法{side newside=q1.poll();int p1=newside.point1;int p2=newside.point2;if(search(p1)!=search(p2)){union(p1, p2);System.out.println(cityname[p1]+" "+cityname[p2]+" 联通");minlength+=newside.lenth;}}System.out.println(minlength);}static class side//边{int lenth;int point1;int point2;public side(int lenth,int p1,int p2) {this.lenth=lenth;this.point1=p1;this.point2=p2;}}
}
输出结果
上海 杭州 联通
广州 深圳 联通
南京 上海 联通
武汉 南京 联通
北京 南京 联通
武汉 广州 联通
28
总结
最小生成树算法理解起来也相对简单,实现起来也不是很难。Kruskal和Prim
主要是贪心算法的两种角度。一个从整体开始找最小边,遇到关联不断合并,另一个从局部开始扩散找身边的最小不断扩散直到生成最小生成树。在学习最小生成树之前最好学习一下dijkstra
算法和并查集,这样在实现起来能够快一点,清晰一点。
力扣1584就是一个最小生成树的入门题,不过哪个有点区别的就是默认所有点是联通的,所以需要你剪枝优化。这里就不带大家一起看啦,有问题下面也可交流!
最后,如果你那天真的获得一大笔资金去修建这么一条昂贵的黄金路线,可以适当采取此方法,另外剩下的大批,苟富贵,勿相忘。。
另外,我将自己csdn写的原创整理成258页数据结构与算法pdf,大家可以关注下面
最小生成树(Prim、Kruskal)算法,秒懂!相关推荐
- 最小生成树:Kruskal算法 和 Prim算法(第23章)
武侠: 飞雪连天射白鹿,笑书神侠倚碧鸳. --金庸十四著作 飞狐外传 .雪山飞狐 .连城诀 .天龙八部 .射雕英雄传 .白马啸西风 .鹿鼎记 .笑傲江湖 .书剑恩仇录 .神雕侠侣 .侠客岛 .倚天屠龙 ...
- 最小生成树的Kruskal算法实现
最近在复习数据结构,所以想起了之前做的一个最小生成树算法.用Kruskal算法实现的,结合堆排序可以复习回顾数据结构.现在写出来与大家分享. 最小生成树算法思想:书上说的是在一给定的无向图G = (V ...
- 数据结构------最小生成树之Kruskal算法
盛年不重来,一日难再晨.及时当勉励,岁月不待人. <杂诗>陶渊明 目录 前言 一.Kruskal的几何思维 二.使用步骤 1.核心思想 2.全部测试代码 总结 前言 最小生成树算法有两种一 ...
- 最小生成树的Kruskal算法-详解
最小生成树的Kruskal算法 一. 什么是最小生成树 1.1 最小生成树定义: 一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边.最 ...
- kruskal算法c语言,最小生成树之Kruskal算法
上一篇文章中提到了最小生成树的Prim算法,这一节继续探讨一下最小生成树的Kruskal算法.什么是最小生成树算法上文已经交代过了,所以我们直接从Kruskal的步骤开始介绍. 1.Kruskal算法 ...
- 最小生成树(Kruskal算法+Prim算法)简单讲解+最小生成树例题 acm寒假集训日记22/1/8
算法简讲部分: Kruskal算法: 基于贪心策略大致过程分为第三步:1. 我们先用结构体把每条边的端点和权值记录下来,然后对每条边按权值进行排序2. 因为 使图连通最少需要n-1 条边,所以我们依次 ...
- ds图—最小生成树_Java: Kruskal算法生成最小生成树(邻接矩阵)
Java: Kruskal算法生成最小生成树(邻接矩阵): package 输出: Kruskal=36: (E,F) (C,D) (D,E) (B,F) (E,G) (A,B) 分析: Java: ...
- HDU 1233 还是畅通工程(最小生成树 Prim+Kruskal)
原题地址 http://acm.hdu.edu.cn/showproblem.php?pid=1233 题意:(最小生成树裸题)有N个村庄,给出村庄两两之间的距离,要求铺设公路,使得任何两个村庄间都可 ...
- python【数据结构与算法】最小生成树之Kruskal算法
我们用现在来模拟一下Kruskal算法,下面给出一个无向图B,我们使用Kruskal来找无向图B的最小生成树. 首先,我们将所有的边都进行从小到大的排序.排序之后根据贪心准则,我们选取最小边(A,D) ...
- 最小生成树之Kruskal算法
图片描述 算法思想 采用贪婪策略,连续的按最小的权选择边,并且当边不产生圈时就把它作为取定的边 算法思路 问题出现 1.怎样选择最小权的边 用个排序算法 2.怎样判断加入的边是否会产生圈 (用到不相交 ...
最新文章
- 分布式文件系统KFS源码阅读与分析(四):RPC实现机制(KfsClient端)
- IE不能為讀(written)問題解決(轉載)
- 20+ Rsync command’s switches and common usages with examples – Unix/Linux--reference
- 移动视频-你选择了谁?
- node和java性能_服务端I/O性能大比拼:Node、PHP、Java和Go(二)
- 小程序 input 换行_小程序 input双向数据绑定
- coredump_filter的设置
- 用vbs运行CMD不显示窗口的方法汇总
- php索引数组转键数组,PHP-Codeigniter:如何从指定索引转换数组值?
- C语言程序设计型考册作业1,C语言程序设计作业 求解答
- 网易云信七鱼市场总监姜菡钰:实战解读增长黑客在B端业务的运用
- Win10怎么隐藏任务栏时间 如何看不到右下角时间
- Unity的GPU Instancing
- 【数据恢复】【傲梅分区助手】
- 测试报告(包括测试总结)
- 想考数据库工程师?你需要了解这些
- js 拖动多div层,, IE,FF下多可行。
- cdn内容分发网络介绍
- 高中数学必修二平面解析几何之两直线的位置关系(归纳与整理)
- 服务器占用cpu启动就死机,CPU使用率高会不会造成死机?为何?
热门文章
- stdthread(2)创建
- 区块链BaaS云服务(35)亦笔科技ODRChain
- (chap6 Http首部) 请求首部字段 RefererTE User-Agent
- 解决Ubuntu与Windows之间无法复制粘贴问题
- DRF_APIView之认证、授权配置
- 数据结构常见算法集锦
- e0312 不存在用户定义的_VistaPro创建自定义变量
- 2020-11-28(不定参数的函数)
- Python3 定向爬虫之“抓取糗事百科图片”
- HttpURLConnection 发送post请求。并将结果以JSONObject对象返回的轮子