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

    文章

    6795

    浏览量

    88730
  • 算法
    +关注

    关注

    23

    文章

    4587

    浏览量

    92478

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

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

收藏 人收藏

    评论

    相关推荐

    嵌入式常用数据结构有哪些

    在嵌入式编程数据结构的选择和使用对于程序的性能、内存管理以及开发效率都具有重要影响。嵌入式系统由于资源受限(如处理器速度、内存大小等),因此对数据结构的选择和使用尤为关键。以下是嵌入式编程中常用的几种
    的头像 发表于 09-02 15:25 357次阅读

    揭秘编程核心:基本数据结构算法思想详解

    描述问题的数据除了各数据元素本身,还要考虑各元素的逻辑关系,主要是一对一的线性关系,一对多的型关系和多对多的图形关系。
    的头像 发表于 04-25 11:51 1014次阅读
    揭秘编程核心:基本<b class='flag-5'>数据结构</b>与<b class='flag-5'>算法</b>思想详解

    探索编程世界的七大数据结构

    结构就像是一颗倒挂的小树,有根、有枝、有叶。它是一种非线性的数据结构,以层级的方式存储数据,顶部是根节点,底部是叶节点。
    的头像 发表于 04-16 12:04 336次阅读

    TASKING编译器是否可以将数据结构设置为 \"打包\"?

    复制到相应的数据结构,应用程序就可以节省解包时间。 是的,我知道访问打包数据结构在内存访问时间性能方面并非最佳。 不过,至少在我的应用,对这种打包
    发表于 03-05 06:00

    矢量与栅格数据结构各有什么特征

    矢量数据结构和栅格数据结构是地理信息系统(GIS)中最常用的两种数据结构。它们在存储和表示地理要素上有着不同的方法和特征。在接下来的文章,我们将详细介绍这两种
    的头像 发表于 02-25 15:06 2216次阅读

    区块链是什么样的数据结构组织

    区块链是一种特殊的数据结构,它以分布式、去中心化的方式组织和存储数据。区块链的核心原理是将数据分布在网络的各个节点上,通过密码学算法保证数据
    的头像 发表于 01-11 10:57 1818次阅读

    C语言数据结构之跳表详解

    大家好,今天分享一篇C语言数据结构相关的文章--跳表。
    的头像 发表于 12-29 09:32 779次阅读
    C语言<b class='flag-5'>数据结构</b>之跳表详解

    redis数据结构的底层实现

    Redis是一种内存键值数据库,常用于缓存、消息队列、实时数据分析等场景。它的高性能得益于其精心设计的数据结构和底层实现。本文将详细介绍Redis常用的数据结构和它们的底层实现。 Re
    的头像 发表于 12-05 10:14 570次阅读

    不同数据结构的定义代码

    数据结构是相互之间存在一种或多种特定关系的数据元素的集合。
    的头像 发表于 11-29 14:13 606次阅读

    与二叉的定义

    结构 是一类重要的 非线性数据结构 ,其中以和二叉最为常用,直观来看,是以分支关系定义
    的头像 发表于 11-24 15:57 1233次阅读
    <b class='flag-5'>树</b>与二叉<b class='flag-5'>树</b>的定义

    redis的五种数据类型底层数据结构

    Redis是一种内存数据存储系统,支持多种数据结构。这些数据结构不仅可以满足常见的存储需求,还能够通过其底层数据结构提供高效的操作和查询。以下是Redis中常用的五种
    的头像 发表于 11-16 11:18 670次阅读

    定时器的实现数据结构选择

    在后端的开发,定时器有很广泛的应用。 比如: 心跳检测 倒计时 游戏开发的技能冷却 redis的键值的有效期等等,都会使用到定时器。 定时器的实现数据结构选择 红黑 对于增删查,时间复杂度为O
    的头像 发表于 11-13 14:22 485次阅读
    定时器的实现<b class='flag-5'>数据结构</b>选择

    ringbuffer数据结构介绍

    最近在研究srsLTE的代码,其中就发现一个有意思的数据结构------ringbuffer。 虽然,这是一个很基本的数据结构,但时,它在LTE这种通信协议栈系统却大行其道,也是很容易被协议
    的头像 发表于 11-13 10:44 1500次阅读
    ringbuffer<b class='flag-5'>数据结构</b>介绍

    epoll的基础数据结构

    一、epoll的基础数据结构 在开始研究源代码之前,我们先看一下 epoll 中使用的数据结构,分别是 eventpoll、epitem 和 eppoll_entry。 1、eventpoll 我们
    的头像 发表于 11-10 10:20 747次阅读
    epoll的基础<b class='flag-5'>数据结构</b>

    Linux内核中使用的数据结构

    Linux内核代码中广泛使用了数据结构算法,其中最常用的两个是链表和红黑。 链表 Linux内核代码大量使用了链表这种数据结构。链表是在解决数组不能动态扩展这个缺陷而产生的一种
    的头像 发表于 11-09 14:24 440次阅读
    Linux内核中使用的<b class='flag-5'>数据结构</b>