算法与数据结构(五) 普利姆与克鲁斯卡尔的最小生成树(Swift版)
上篇博客我们聊了图的物理存储结构邻接矩阵和邻接链表,然后在此基础上给出了图的深度优先搜索和广度优先搜索。本篇博客就在上一篇博客的基础上进行延伸,也是关于图的。今天博客中主要介绍两种算法,都是关于最小生成树的,一种是Prim算法,另一个是Kruskal算法。这两种算法是很经典的,也是图中比较重要的算法了。
今天博客会先聊一聊Prim算法是如何生成最小生成树的,然后给出具体步骤的示例图,最后给出具体的代码实现,并进行测试。当然Kruskal算法也是会给出具体的示例图,然后给出具体的代码和测试用例。当然本篇博客中的Demo是在上篇博客的基础上进行实现的。因为在上篇博客中我们已经创建好了现成的图了,本篇博客就拿过来直接使用。
在本篇博客的开头呢,先简单的聊一下什么是最小生成树。最小生成树是原图的最小连通子图,也就是说该子图是连通的并且没有多余的边,更不会形成回路。最重要的是最小生成树的所有边的权值相加最小,这也是最小生成树的来源。与现实生活中联系起来那就是一些村庄要通电话线,如何让每个村都可以通电话线并且最省材料。换句话说,是每个村庄连通,并且总线路最短,如果线连接完毕后,其实就是我们本篇博客要聊的最小生成树。
一、普利姆算法
接下来我们就来聊Prim算法。其实Prim算法创建最小生成树的主要思路就是从候选节点中选择最小的权值添加到最小生成树中。下图是我们之前创建的图使用Prim算法创建最小生成树的完整过程。红色的边就是每一步所对应的候选节点做连的弧,从这些候选的边中选出权值最小的边添加到最小生成树中,我们可以将其视为转正的过程。
一个节点转正后,将其转正节点所连的弧度视为候选弧度,当然这些候选弧度所连的节点必须是最小生成树上以外的点。如果候选弧度所连的点位于最小生成树上,那么将该候选节点抛弃。直到无候选弧度时,最小生成树的创建就完成了。
下图就很好的表述了这个过程,每一步候选节点间的连接使用红色标记,而转正的节点间的弧度使用黑色表示。按照下方这个思路,最终就会生成我们需要的最小生成树。
1.Prim算法示意图解析
(0):就是我们上篇博客所创建的图的结构,并且每条弧度都有权值。
(1):我们以A节点为最小生成树的根节点来创建最小生成树,与A节点相连的是B和F节点,所以这两个节点是本步骤的候选节点。因为(A--10--B) < (A--11--F),所以我们将候选节点中权值最小的B结点进行转正。
(2):将B转正,并且使用黑线进行标注,现在A, B节点都位于最小生成树中。B节点转正后,我们将那些与B节点相连但不在最小生成树中的节点添加到候选节点的集合中,此时最小生成树的候选节点有: (B--18--C),(B--12--I), (B--16--G),(A--11--F)。
(3):从上一步留下的候选节点中,我们可以看出 A--11--F 这条边的权值最小,所以将F结点转正加入到最小生成树中。因为E结点又与刚转正的F结点相连接,所以将E节点添加进候选结点集合中。此时最小生成树的候选节点有: (B--18--C),(B--12--I), (B--16--G),(F--17--G), (F--26--E)。
(4):其中B--12--I这条与候选结点所连的边的权值最小,我们将I转正,并且将于I连的D节点添加进候选节点中。此时最小生成树的候选节点有: (B--18--C), (B--16--G),(F--17--G), (F--26--E),(I--8--C), (I--21--D)。
(5):此刻的候选结点有C, G, E, D。因为I -- 8 -- C在候选结点中的弧度最小,所以讲C进行转正。因为C节点转正,所以将到C节点的候选结点移除。将与C节点连接的点添加进行候选结点集合中,此时最小生成树的候选弧度有: (B--16--G),(F--17--G), (F--26--E), (I--21--D),(C--22--D)。
(6):从上述候选弧度中,我们容易看出(B--16--G)的权值最小,所以讲G节点进行转正。G节点转正后,那么候选节点的集合为:(F--26--E), (I--21--D),(C--22--D),(G--19--H),(G--24--D)。
(7):还是选最小的将其转正,上述候选集合中最小的权值就是(G--19--H),所以讲结点H转正。将(H--7--E), (H--16--D)添加到候选集合当中,此时候选集合为:(F--26--E), (I--21--D),(C--22--D),(G--24--D),(H--7--E), (H--16--D)。
(8):上述候选集中(H--7--E)最小,所以将E结点进行转正,E节点转正后的候选节点为:(I--21--D),(C--22--D),(G--24--D),(H--16--D),(E--20--D)。
(9):在候选集合中通往D节点的权值最小的是(H--16--D),所以D节点转正,与H节点相连。因为D节点已转正,那么候选节点中所有到达D节点的弧度都得从候选节点中进行移除,那么此刻候选集合为空。当候选集合为空时,就说明我们的最小生成树就生成完毕了。
(10):就是我们最终生成的最小生成树。
2.上述过程的代码实现
如果理解了上述过程,那么给出代码的实现并不困难。我们以邻接链表为例,邻接矩阵的最小生成树的Prim算法的表示方式在此就不做过多赘述了,不过Github上分享的Demo是有关于邻接矩阵的Prim算法的相关内容的。下方这个代码截图就是Prim算法在邻接链表中的具体实现。
在下方截图的方法中,第一个参数index是上次转正添加到最小生成树的节点在邻接链表的数组中的索引。第二个参数leafNotes是可以转正的候选叶子结点。第三个参数adjvex是已经添加到最小生成树上的节点。
下方代码主要分为下方几步:
寻找与上次转正的结点所连的并且不在adjvex数组中的结点,将这些节点添加到候选集合中。
从候选集合中找出权值最小的那条边,并且确定与该边所连的节点可以转正。
将上一步寻找的结点添加到我们新的邻接链表中。
将已经转正的节点从候选结合中删除。
将已经转正的节点添加进adjves数组中。
递归这个刚刚转正的节点。
3.测试结果
下方就是我们上述代码所创建的最小生成树,当然我们依然是采用邻接链表来存储我们的最小生成树,下方这个结构就是我们的最小生成树的邻接链表的存储结构,以及对该最小生成树的遍历的结果。
上述是邻接链表上生成的最小生成树以及遍历的结果,下方是邻接矩阵生成的最小生成树以及遍历的结果。
二、克鲁斯卡尔算法
上一部分我们详细的讲解了Prim算法的整个过程,接下来就来聊一下最小生成树的另一个经典的算法Kruskal算法。 Kruskal算法的核心思想就是先将每条边按着权值从小到大进行排序,然后从有序的集合中以此取出最小的边添加到最小生成树中,不过要保证新添加的边与最小生成树上的边不构成回路。下方会给出具体的算法步骤并且给出具体的代码实现。
1.Kruskal算法原理图
首先我们得给节点间的关系也就是我们之前用到的relation数组进行排序,按照权值的大小依次排序,下方就是我们排序的结果。我们构建“最小生成树”所需要的边就从下方的关系中依次取出,在加入最小生成树之前,我们要先判断取出的边加入最小生成树中后是否构成回路。如果不构成回路就添加进最小生成树中,如果构成回路,那么就将该边抛弃。下方就是我们按照权值排好的关系集合。
下方就是从上述集合中取出边,一个一个的往新的邻接链表中插入数据,插入边时我们要判断是否会在最小生成树中形成回路,如果形成回路,那么就将该边抛弃并获取下一条边。
2.寻找节点的尾部节点
在上述算法中,判断新添加的边是否在最小生成树中构成回路是该算法的关键。下方就是判断要连接的两个节点是否在最小生成树中形成回路,当两个节点的尾部节点不相等时,就说明将两个点相连接后不会在最小生成树中构成回路。当两个节点有着共同的尾部节点时,就说明连接后会在最小生成树中形成回路,原理图如下所示:
下方这个方法就是寻找一个节点的尾部节点,parent中存储的就是索引对应节点的尾部节点的索引,下方代码片段就是将寻找的该节点的尾部节点的索引进行返回。
3、Kruskal算法的具体实现
下方代码段就是Kruskal算法的具体实现,首先我们先通过configMiniTree()方法来初始化一个邻接链表,此邻接链表用来存储我们的最小生成树。然后我们对节点与弧度的集合根据权值从小到大排序。排序后,通过for循环对这个有序的集合进行遍历,将那些不构成回路的边添加进我们的最小生成树即可。具体代码如下所示。
/**
创建最小生成树: Kruskal
*/
func createMiniSpanTreeKruskal(){
print("克鲁斯卡尔算法:")
configMiniTree()
//对权值从小到大进行排序
let sortRelation = relation.sorted { (item1, item2) -> Bool in
return Int(item1.2 as! NSNumber) < Int(item2.2 as! NSNumber)
} //记录节点的尾部节点,避免出现闭环
var parent = Array.init(repeating: -1, count: miniTree.count) for item in sortRelation {
let beginNoteIndex = self.relationDic[item.0 as! String]!
let endNoteIndex = self.relationDic[item.1 as! String]!
let weightNumber = item.2 as! Int let preEndIndex = findEndIndex(parent: parent, index: beginNoteIndex)
let nextEndIndex = findEndIndex(parent: parent, index: endNoteIndex) print("\(beginNoteIndex)--\(weightNumber)-->\(endNoteIndex)") if preEndIndex != nextEndIndex { parent[preEndIndex] = nextEndIndex //更新尾部节点
insertNoteToMiniTree(preIndex: beginNoteIndex,
linkIndex: endNoteIndex,
weightNumber: weightNumber);
}
} displayGraph(graph: miniTree)
} ///将合适的节点插入到新的邻接链表中
private func insertNoteToMiniTree(preIndex: Int,
linkIndex: Int,
weightNumber: Int) {
let note = GraphAdjacencyListNote(data: linkIndex as AnyObject,
weightNumber: weightNumber,
preNoteIndex: preIndex)
note.next = miniTree[preIndex].next
miniTree[preIndex].next = note
}
篇幅有限,今天博客就先到这吧,本篇博客的完整Demo依然会在github上进行分享,分享地址如下:
github分享地址:https://github.com/lizelu/DataStruct-Swift/tree/master/Graph
算法与数据结构(五) 普利姆与克鲁斯卡尔的最小生成树(Swift版)的更多相关文章
- 算法与数据结构(一) 线性表的顺序存储与链式存储(Swift版)
温故而知新,在接下来的几篇博客中,将会系统的对数据结构的相关内容进行回顾并总结.数据结构乃编程的基础呢,还是要不时拿出来翻一翻回顾一下.当然数据结构相关博客中我们以Swift语言来实现.因为Swift ...
- c/c++ 用普利姆(prim)算法构造最小生成树
c/c++ 用普利姆(prim)算法构造最小生成树 最小生成树(Minimum Cost Spanning Tree)的概念: 假设要在n个城市之间建立公路,则连通n个城市只需要n-1条线路.这时 ...
- 普利姆算法(prim)
普利姆算法(prim)求最小生成树(MST)过程详解 (原网址) 1 2 3 4 5 6 7 分步阅读 生活中最小生成树的应用十分广泛,比如:要连通n个城市需要n-1条边线路,那么怎么样建设才能使工程 ...
- 最小生成树-普利姆算法eager实现
算法描述 在普利姆算法的lazy实现中,参考:普利姆算法的lazy实现 我们现在来考虑这样一个问题: 我们将所有的边都加入了优先队列,但事实上,我们真的需要所有的边吗? 我们再回到普利姆算法的lazy ...
- 最小生成树-普利姆算法lazy实现
算法描述 lazy普利姆算法的步骤: 1.从源点s出发,遍历它的邻接表s.Adj,将所有邻接的边(crossing edges)加入优先队列Q: 2.从Q出队最轻边,将此边加入MST. 3.考察此边的 ...
- 最小生成树-普利姆(Prim)算法
最小生成树-普利姆(Prim)算法 最小生成树 概念:将给出的所有点连接起来(即从一个点可到任意一个点),且连接路径之和最小的图叫最小生成树.最小生成树属于一种树形结构(树形结构是一种特殊的图),或者 ...
- 图论---最小生成树----普利姆(Prim)算法
普利姆(Prim)算法 1. 最小生成树(又名:最小权重生成树) 概念:将给出的所有点连接起来(即从一个点可到任意一个点),且连接路径之和最小的图叫最小生成树.最小生成树属于一种树形结构(树形结构是一 ...
- POJ-2421-Constructing Roads(最小生成树 普利姆)
Time Limit: 2000MS Memory Limit: 65536K Total Submissions: 26694 Accepted: 11720 Description The ...
- 最小生成树入门(克鲁斯卡尔+普利姆 hdu1233)
克鲁斯卡尔 #include <set> #include <map> #include <queue> #include <stack> #inclu ...
随机推荐
- 详解MariaDB数据库的外键约束
1.什么是外键约束 外键约束(foreign key)就是表与表之间的某种约定的关系,由于这种关系的存在,我们能够让表与表之间的数据,更加的完整,关连性更强. 关于数据表的完整性和关连性,可以举个例子 ...
- struts2-第一章-基础用法3
一,结果类型配置 在之前servlet学习中,知道网页页面路径跳转有两种方式,内部跳转(请求转发)和外部跳转(重定向),两者的区别,内部跳转浏览器地址不会变化 可以保存上一次请求的数据 外部跳转浏览器 ...
- TCP和UDP的区别以及使用python服务端客户端简单编程
一.TCP.UDP区别总结 1.TCP面向连接(如打电话要先拨号建立连接):UDP是无连接的,即发送数据之前不需要建立连接 2.TCP提供可靠的服务,也就是说,通过TCP连接传送的数据,无差错,不丢失 ...
- SEH exception with code 0xc0000005 thrown in the test body
在用Visual Studio时遇到这个报错.原因:访问了非法的内存地址. 这个问题不应该被忽略,通常是代码有bug. 解决办法: VS2013: 菜单->Debug->Exception ...
- ***OneinStack交互安装FAQ和管理服务常用命令
转自: https://oneinstack.com/install/ 自动生成oneinstack安装连接: https://oneinstack.com/auto/ (进入linux系统后复杂上 ...
- PHP7.* AES的加密解密
之前写过一篇: PHP AES的加密解密-----[弃用] 使用的是php5.*之前的mcrypt_decrypt 函数,该函数已经在php7.1后弃用了,上马的是openssl的openssl_en ...
- Mac smartsvn破解及license文件
第一步:去官网下载自己系统smartsvn版本文件 下载地址:http://www.smartsvn.com/download 第二步:破解 (1) 将文件解压到系统路径:/opt/smartsvn ...
- eclipse乱码解决
设置utf-8 1.点击window>preferences>content types 2.点击右侧Text 3.点击Java Source File 4.下面输入UTF-8 5.点击u ...
- Docker 学习8 Dockerfile详解2
一.继续上章节Docker学习7 CMD命令后. 11.ENTRYPOINT a.容器启动后相当于会启动ENTRYPOINT + CMD 命令,CMD相当于参数传给entrypoint的 [root@ ...
- Excel—错误解释
1.#####! 如果单元格所含的数字.日期或时间比单元格宽,或者单元格的日期时间公式产生了一个负值,就会产生#####!.这个看起来比较简单,大家应该都了解吧. 解决方法:如果单元格所含的数字 ...