0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

数据结构与算法中什么是最小生成树

算法与数据结构 来源:bigsai 作者:bigsai 2021-10-28 17:13 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

前言

在数据结构与算法图论中,(生成)最小生成树算法是一种常用并且和生活贴切比较近的一种算法。但是可能很多人对概念不是很清楚,什么是最小生成树?

一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边。最小生成树可以用kruskal(克鲁斯卡尔)算法或prim(普里姆)算法求出。

通俗易懂的讲就是最小生成树包含原图的所有节点而只用最少的边最小的权值距离。因为n个节点最少需要n-1个边联通,而距离就需要采取某种策略选择恰当的边。

学习最小生成树实现算法之前我们要先高清最小生成树的结构和意义所在。咱么首先根据一些图更好的祝你理解。

一个故事

在城市道路规划中,是一门很需要科学的研究(只是假设学习不必当真)。在公路时代城市联通的主要矛盾是时间慢,而造价相比运输时间是次要矛盾。所以在公路时代我们尽量使得城市能够直接联通,缩短城市联系时间,而稍微考虑建路成本!随着科技发展、高级铁路、信息传输相比公路运输快非常非常多,从而事件的主要矛盾从运输时间转变为造价成本,故有时会关注联通所有点的路程(最短),这就用到最小生成树算法。

城市道路铺设可能经历以下几个阶段。

  • 初始,各个城市没有高速公路(铁路)。

  • 政府打算各个城市铺设公路(铁路),每个城市都想成为交通枢纽,快速到达其他城市,但每个城市都有这种想法,如果实现下去造价太昂贵。并且造成巨大浪费。

  • 最终国家选择一些主要城市进行联通,有个别城市只能稍微绕道而行,而绕道太远的、人流量多的国家考虑新建公路(铁路),适当提高效率。

不过上面铁路规划上由于庞大的人口可能不能够满足与"有铁路"这个需求,人们对速度、距离、直达等条件一直在追求中……

但是你可以想象这个场景:有些东西造假非常非常昂贵,使用效率非常高,我这里假设成黄金镶钻电缆铺设,所以各个城市只要求不给自己落下,能通上就行(没人敢跳了吧)。

要从有环图中选取代价和最小的路线一方面代价最小(总距离最小最省黄金)另一方面联通所有城市

然而根据上图我们可以得到以下最小生成树,但是最么生成这个最小生成树,就是下面要讲的了。

而类似的还有局部区域岛屿联通修桥海底通道这些高成本的都多多少少会运用。

Kruskal算法

上面介绍了最小生成树是什么,现在需要掌握和理解最小生成树如何形成。给你一个图,用一个规则生成一个最小生成树。而在实现最小生成树方面有prim和kruskal算法,这两种算法的策略有所区别,但是时间复杂度一致。

Kruskal算法,和前面讲到的并查集关系很大,它的主要思想为:

先构造一个只含 n 个顶点、而边集为空的子图,把子图中各个顶点看成各棵树上的根结点,之后,从网的边集 E 中选取一条权值最小的边,若该条边的两个顶点分属不同的树,则将其加入子图,即把两棵树合成一棵树,反之,若该条边的两个顶点已落在同一棵树上,则不可取,而应该取下一条权值最小的边再试之。依次类推,直到森林中只有一棵树,也即子图中含有 n-1 条边为止。

简而言之,Kruskal算法进行调度的单位是边,它的信仰为:所有边能小则小,算法的实现方面要用到并查集判断两点是否在同一集合。

而算法的具体步骤为:

  1. 将图中所有边对象(边长、两端点)依次加入集合(优先队列)q1中。初始所有点相互独立

  2. 取出集合(优先队列)q1最小边,判断边的两点是否联通。

  3. 如果联通说明两个点已经有其它边将两点联通了,跳过,如果不连通,则使用union(并查集合并)将两个顶点合并,这条边被使用(可以储存或者计算数值)。

  4. 重复2,3操作直到集合(优先队列)q1为空。此时被选择的边构成最小生成树。

Prim算法

除了Kruskal算法以外,普里姆算法(Prim算法)也是常用的最小生成树算法。虽然在效率上差不多。但是贪心的方式和Kruskal完全不同。

prim算法的核心信仰是:从已知扩散寻找最小。它的实现方式和Dijkstra算法相似但稍微有所区别,Dijkstra是求单源最短路径,而每计算一个点需要对这个点重新更新距离,而prim不用更新距离。直接找已知点的邻边最小加入即可!primkruskal算法都是从边入手处理。

对于具体算法具体步骤,大致为:

  1. 寻找图中任意点,以它为起点,它的所有边V加入集合(优先队列)q1,设置一个boolean数组bool[]标记该位置(边有两个点,每次加入没有被标记那个点的所有边)。

  2. 从集合q1找到距离最小的那个边v1判断边是否存在未被标记的一点`p`,如果p不存在说明已经确定过那么跳过当前边处理,如果未被标(访问)记那么标记该点p,并且与p相连的未知点(未被标记)构成的边加入集合q1,边v1(可以进行计算距离之类,该边构成最小生成树).

  3. 重复1,2直到q1为空,构成最小生成树 !

大体步骤图解为:

因为prim从开始到结束一直是一个整体在扩散,所以不需要考虑两棵树合并的问题,在这一点实现上稍微方便了一点。

当然,要注意的是最小生成树并不唯一,甚至同一种算法生成的最小生成树都可能有所不同,但是相同的是无论生成怎样的最小生成树:

  • 能够保证所有节点连通(能够满足要求和条件)

  • 能够保证所有路径之和最小(结果和目的相同)

  • 最小生成树不唯一,可能多样的

代码实现

上面分析了逻辑实现。下面我们用代码简单实现上述的算法。

prim

package图论;

importjava.util.ArrayList;
importjava.util.Arrays;
importjava.util.Comparator;
importjava.util.List;
importjava.util.PriorityQueue;
importjava.util.Queue;

publicclassprim{

publicstaticvoidmain(String[]args){
intminlength=0;//最小生成树的最短路径长度
intmax=66666;
Stringcityname[]={"北京","武汉","南京","上海","杭州","广州","深圳"};
intcity[][]={
{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}//深圳——杭州、广州
};//地图

booleanistrue[]=newboolean[7];
//南京
Queueq1=newPriorityQueue(newComparator(){
publicintcompare(sideo1,sideo2){
//TODOAuto-generatedmethodstub
returno1.lenth-o2.lenth;
}
});
for(inti=0;i<7;i++)
{
if(city[2][i]!=max)
{
istrue[2]=true;
q1.add(newside(city[2][i],2,i));
}
}
while(!q1.isEmpty())
{
sidenewside=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(inti=0;i<7;i++)
{
if(!istrue[i])
{
q1.add(newside(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(inti=0;i<7;i++)
{
if(!istrue[i])
{
q1.add(newside(city[newside.point2][i],newside.point2,i));
}
}
}
}

}
System.out.println(minlength);
}

staticclassside//边
{
intlenth;
intpoint1;
intpoint2;
publicside(intlenth,intp1,intp2){
this.lenth=lenth;
this.point1=p1;
this.point2=p2;
}
}

}

输出结果:

上海 南京 联通
杭州 上海 联通
武汉 南京 联通
北京 南京 联通
广州 武汉 联通
深圳 广州 联通
28

Kruskal:
package图论;

importjava.util.Comparator;
importjava.util.PriorityQueue;
importjava.util.Queue;

import图论.prim.side;
/*
*作者:bigsai(公众号)
*/
publicclasskruskal{

staticinttree[]=newint[10];//bing查集
publicstaticvoidinit(){
for(inti=0;i<10;i++)//初始
{
tree[i]=-1;
}
}
publicstaticintsearch(inta)//返回头节点的数值
{
if(tree[a]>0)//说明是子节点
{
returntree[a]=search(tree[a]);//路径压缩
}
else
returna;
}
publicstaticvoidunion(inta,intb)//表示a,b所在的树合并小树合并大树(不重要)
{
inta1=search(a);//a根
intb1=search(b);//b根
if(a1==b1){//System.out.println(a+"和"+b+"已经在一棵树上");
}
else{
if(tree[a1]//这个是负数,为了简单减少计算,不在调用value函数
{
tree[a1]+=tree[b1];//个数相加注意是负数相加
tree[b1]=a1;//b树成为a的子树,直接指向a;
}
else
{
tree[b1]+=tree[a1];//个数相加注意是负数相加
tree[a1]=b1;//b树成为a的子树,直接指向a;
}
}
}
publicstaticvoidmain(String[]args){
//TODOAuto-generatedmethodstub
init();
intminlength=0;//最小生成树的最短路径长度
intmax=66666;
Stringcityname[]={"北京","武汉","南京","上海","杭州","广州","深圳"};
booleanjud[][]=newboolean[7][7];//加入边需要防止重复比如ba和ab等价的
intcity[][]={
{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}
};//地图
booleanistrue[]=newboolean[7];
//南京
Queueq1=newPriorityQueue(newComparator(){//优先队列存边+
publicintcompare(sideo1,sideo2){
//TODOAuto-generatedmethodstub
returno1.lenth-o2.lenth;
}
});
for(inti=0;i<7;i++)
{
for(intj=0;j<7;j++)
{
if(!jud[i][j]&&city[i][j]!=max)//是否加入队列
{
jud[i][j]=true;jud[j][i]=true;
q1.add(newside(city[i][j],i,j));
}
}
}
while(!q1.isEmpty())//执行算法
{
sidenewside=q1.poll();
intp1=newside.point1;
intp2=newside.point2;
if(search(p1)!=search(p2))
{
union(p1,p2);
System.out.println(cityname[p1]+""+cityname[p2]+"联通");
minlength+=newside.lenth;
}
}
System.out.println(minlength);


}
staticclassside//边
{
intlenth;
intpoint1;
intpoint2;
publicside(intlenth,intp1,intp2){
this.lenth=lenth;
this.point1=p1;
this.point2=p2;
}
}
}

输出结果

上海 杭州 联通
广州 深圳 联通
南京 上海 联通
武汉 南京 联通
北京 南京 联通
武汉 广州 联通
28

总结

最小生成树算法理解起来也相对简单,实现起来也不是很难。Kruskal和Prim主要是贪心算法的两种角度。一个从整体开始找最小边,遇到关联不断合并,另一个从局部开始扩散找身边的最小不断扩散直到生成最小生成树。在学习最小生成树之前最好学习一下dijkstra算法和并查集,这样在实现起来能够快一点,清晰一点。

力扣1584就是一个最小生成树的入门题,不过哪个有点区别的就是默认所有点是联通的,所以需要你剪枝优化。

责任编辑:haq
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 数据
    +关注

    关注

    8

    文章

    7347

    浏览量

    95004
  • 算法
    +关注

    关注

    23

    文章

    4800

    浏览量

    98500

原文标题:最小生成树,秒懂!

文章出处:【微信号:TheAlgorithm,微信公众号:算法与数据结构】欢迎添加关注!文章转载请注明出处。

收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    生成式AI赋能工程师挖掘非结构数据价值

    您是否知道,生成式 AI(GenAI)可以帮助工程师在几秒钟内诊断汽车故障,甚至在设备出现问题之前预测潜在失效?GenAI 正在通过加速数据分析和算法开发,让这些场景从设想走向现实,使工程师能够充分发挥专业知识,挖掘可执行的洞察
    的头像 发表于 02-28 10:24 588次阅读

    无线倾角传感器在古监测的应用:以科技守护活文物的结构安全

    无线倾角传感器在古监测的应用:以科技守护活文物的结构安全
    的头像 发表于 01-09 11:38 758次阅读
    无线倾角传感器在古<b class='flag-5'>树</b>监测<b class='flag-5'>中</b>的应用:以科技守护活文物的<b class='flag-5'>结构</b>安全

    行业特定的生成式 AI 能力如何形成:面向中国企业的场景化解决方案模型

    随着生成式 AI 在国内加速落地,越来越多企业意识到:单靠通用大模型,并不能覆盖行业的复杂流程与专业需求。金融、制造、能源、零售、医疗等行业各自拥有不同的业务逻辑、监管要求与数据结构,而企业真正
    的头像 发表于 12-02 09:33 550次阅读

    C语言的常见算法

    ; } return *str1 - *str2; } ``` ## 6. 数据结构相关算法 ### 链表反转 ```c struct Node* reverseList(struct Node
    发表于 11-24 08:29

    8种常用的CRC算法分享

    CRC 计算单元可按所选择的算法和参数配置来生成数据流的 CRC 码。有些应用,可利用 CRC 技术来验证数据的传输和存储的完整性。 8 种常用的 CRC
    发表于 11-13 07:25

    通过优化代码来提高MCU运行效率

    选择时间复杂度低的算法。 根据访问模式选择数据结构。频繁查找用哈希表,有序数据用二叉等。 查表法:对于复杂的数学计算(如sin, log),或者协议解析,预先计算好结果存于数组
    发表于 11-12 08:21

    国密系列算法简介及SM4算法原理介绍

    使用了Feistel结构(分组密码的一种对称结构),其中密钥扩展部分也使用了Feistel结构,所以对数据和密钥的处理流程极为相似。下面对
    发表于 10-24 08:25

    Verilog实现使用Booth编码和Wallace的定点补码乘法器原理

    是Wallace结构和CSA结构的对比。其结构的关键特性在于利用不规则的树形结构对所有的准备好输入
    发表于 10-23 08:01

    e203乘法运算结构算法原理

    模块,每一周期产生的部分积与之前累积的部分积可以通过ALU的数据通路部分传至ALU的加法器相加。所以乘法器的设计本身并不需要额外的加法器。由于E203所有需要计算加法的模块都采用了这种巧妙
    发表于 10-22 06:43

    分享一个嵌入式学习阶段规划

    (如指针实现链表增删查改),排查内存泄漏、野指针数据结构算法:掌握线性表 / 栈 / 结构,学查找 / 排序算法,手写代码(如二分查找
    发表于 09-12 15:11

    小红书:通过商品标签API自动生成内容标签,优化社区推荐算法

    推荐系统的准确性和用户体验。整个过程结构清晰,分为三个步骤:API集成、标签自动生成算法优化。 第一步:商品标签API的集成与数据获取 小红书通过开放API接口,实时获取商品的
    的头像 发表于 09-10 16:46 1212次阅读
    小红书:通过商品标签API自动<b class='flag-5'>生成</b>内容标签,优化社区推荐<b class='flag-5'>算法</b>

    嵌入式从入门到进阶,怎么学?

    系统原理图。 数据结构优先学 数组、链表、队列(嵌入式底层算法核心),暂跳过复杂结构(如红黑)。 计算机原理了解 CPU 架构、总线系统、存储机制(把嵌入式系统当 “缩小版计算机”
    发表于 09-02 09:44

    【HZ-T536开发板免费体验】6、使用protoc-gen-gorm生成标准化的数据结构

    插件(生成常规的序列化数据结构) go get -u github.com/golang/protobuf/protoc-gen-go 以及gorm的插件(生成gorm对应的数据库)
    发表于 08-26 00:32

    程序设计与数据结构

    的地址)出发,采用推导的方式,深入浅出的分析了广大C程序员学习和开发遇到的难点。 2. 从方法论的高度对C语言在数据结构算法方面的应用进行了深入讲解和阐述。 3. 讲解了绝大多数C程序员开发
    发表于 05-13 16:45

    PanDao:实际约束条件下成像系统的初始结构生成

    的是,寻找合适的初始设计方案以进行后续适配与优化,已经被证明是一项艰巨的工作。为避免这一耗时流程,本次研究的目标是从既定规格与约束条件中直接生成多种优质的初始结构。此研究将会为光学设计师带来两大好处:其一
    发表于 05-07 08:57