上篇博客我们聊了图的物理存储结构邻接矩阵和邻接链表,然后在此基础上给出了图的深度优先搜索和广度优先搜索。本篇博客就在上一篇博客的基础上进行延伸,也是关于图的。今天博客中主要介绍两种算法,都是关于最小生成树的,一种是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是已经添加到最小生成树上的节点。

下方代码主要分为下方几步:

  1. 寻找与上次转正的结点所连的并且不在adjvex数组中的结点,将这些节点添加到候选集合中。

  2. 从候选集合中找出权值最小的那条边,并且确定与该边所连的节点可以转正。

  3. 将上一步寻找的结点添加到我们新的邻接链表中。

  4. 将已经转正的节点从候选结合中删除。

  5. 将已经转正的节点添加进adjves数组中。

  6. 递归这个刚刚转正的节点。

  

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版)的更多相关文章

  1. 算法与数据结构(一) 线性表的顺序存储与链式存储(Swift版)

    温故而知新,在接下来的几篇博客中,将会系统的对数据结构的相关内容进行回顾并总结.数据结构乃编程的基础呢,还是要不时拿出来翻一翻回顾一下.当然数据结构相关博客中我们以Swift语言来实现.因为Swift ...

  2. c/c++ 用普利姆(prim)算法构造最小生成树

    c/c++ 用普利姆(prim)算法构造最小生成树 最小生成树(Minimum Cost Spanning Tree)的概念: ​ 假设要在n个城市之间建立公路,则连通n个城市只需要n-1条线路.这时 ...

  3. 普利姆算法(prim)

    普利姆算法(prim)求最小生成树(MST)过程详解 (原网址) 1 2 3 4 5 6 7 分步阅读 生活中最小生成树的应用十分广泛,比如:要连通n个城市需要n-1条边线路,那么怎么样建设才能使工程 ...

  4. 最小生成树-普利姆算法eager实现

    算法描述 在普利姆算法的lazy实现中,参考:普利姆算法的lazy实现 我们现在来考虑这样一个问题: 我们将所有的边都加入了优先队列,但事实上,我们真的需要所有的边吗? 我们再回到普利姆算法的lazy ...

  5. 最小生成树-普利姆算法lazy实现

    算法描述 lazy普利姆算法的步骤: 1.从源点s出发,遍历它的邻接表s.Adj,将所有邻接的边(crossing edges)加入优先队列Q: 2.从Q出队最轻边,将此边加入MST. 3.考察此边的 ...

  6. 最小生成树-普利姆(Prim)算法

    最小生成树-普利姆(Prim)算法 最小生成树 概念:将给出的所有点连接起来(即从一个点可到任意一个点),且连接路径之和最小的图叫最小生成树.最小生成树属于一种树形结构(树形结构是一种特殊的图),或者 ...

  7. 图论---最小生成树----普利姆(Prim)算法

    普利姆(Prim)算法 1. 最小生成树(又名:最小权重生成树) 概念:将给出的所有点连接起来(即从一个点可到任意一个点),且连接路径之和最小的图叫最小生成树.最小生成树属于一种树形结构(树形结构是一 ...

  8. POJ-2421-Constructing Roads(最小生成树 普利姆)

    Time Limit: 2000MS   Memory Limit: 65536K Total Submissions: 26694   Accepted: 11720 Description The ...

  9. 最小生成树入门(克鲁斯卡尔+普利姆 hdu1233)

    克鲁斯卡尔 #include <set> #include <map> #include <queue> #include <stack> #inclu ...

随机推荐

  1. JavaScript 当月第一天和最后一天

    1. 概述 1.1 说明 在项目过程中,有时候需要默认展示一个月的查询条件,即当月的第一天和最后一天. 2. 代码 2.1 代码示例 直接调用getFirstAndLastDay()即可得到当月的第一 ...

  2. EtherNet/IP 协议结构

    一.Ethernet/IP 协议 将标准的TCP/IP以太网延伸 到工业实时控制并和通用工业协议(CIP)结合,将很好地帮助用户获得更加开放集成的工业自动化和信息化的整体解决方案.EtherNet/I ...

  3. Hive学习笔记 --Permission denied: user=anonymous, access=READ

    执行select语句报错 Error: java.io.IOException: org.apache.hadoop.security.AccessControlException: Permissi ...

  4. CentOS7像外部163邮箱发送邮件

    我们在运维过程中,为了随时了解服务器的工作状态,出现问题随时提醒,像个人邮箱发送邮件是必须的,但是刚刚安装好的系统是无法发送邮件的.需要们进行一些配置和程序的安装,我安装完系统后,自带mail12.5 ...

  5. python的学习之路(一)

    1.python的简介 python的创始人为吉多·范罗苏姆(Guido van Rossum).1989年的圣诞节期间,吉多·范罗苏姆为了在阿姆斯特丹打发时间,决心开发一个新的脚本解释程序,作为AB ...

  6. ansible字符串的处理

    ansible中字符串的处理 from_json json_query join select selectattr map list trim 列表和字典的处理 combine

  7. Win7共享文件夹简单?这个共享问题可以难倒90%的人

    信息化社会,没有哪个公司不用电脑办公了.一个办公室里面的同事相互之间利用系统的共享功能,共享一些文件和软件已经是司空见惯的了,这个不需要多么复杂的操作.我们使用最多的windows7操作系统就能很方便 ...

  8. Java 多线程系列 CountDownLatch

    CountDownLatch 一个或多个线程等待其他线程完成操作后在在执行 CountDownLatch通过一个计数器来实现,await方法阻塞直到 countDown() 调用计数器归零之后释放所有 ...

  9. Web程序-----批量生成二维码并形成一张图片

    需求场景:客户根据前台界面列表所选择的数据,根据需要的信息批量生成二维码并形成一张图片,并且每张图片显示的二维码数量是固定的,需要分页(即总共生成的二维码图片超出每页显示的需另起一页生成),并下载到客 ...

  10. Map接口下的集合和泛型理解

    一.Map接口 1. Map接口就是最顶层了,上面没有继承了.Map是一个容器接口,它与前面学的List.Set容器不同的是前面学的这些容器,一次只能传入一个元素,但是Map容器一次可以传入一对元素( ...