热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

【数据结构基础整理】图05:普里姆算法详解

详解最小生成树的普里姆算法0x01.关于普里姆算法普里姆算法

详解最小生成树的普里姆算法


0x01.关于普里姆算法

普里姆算法(Prim算法),图论中的一种算法,可在加权连通图里搜索最小生成树。意即由此算法搜索到的边子集所构成的树中,不但包括了连通图里的所有顶点(英语:Vertex (graph theory)),且其所有边的权值之和亦为最小。

0x02.生成树和最小生成树

既然普里姆算法是关于最小生成树的算法,那么什么是什么是生成树和最小生成树呢?

生成树定义:

一个连通图的生成树是一个极小连通子图,它含有图中全部n个顶点和n-1条边。

简单理解生成树:

生成树简而言之就是由图去生成一颗树,这颗树包含了图的全部顶点。

那么这里应该有几个生成树应该满足的条件:

1.不能有任何闭合得部分,因为闭合了就不叫树了。

2.得包含全部的顶点。

3.满足其它树的特征。

理解最小生成树:

最小生成树,就是生成一棵树,所花费的代价最小,也就是树所包含的所有边的权值和最小。

0x03.普里姆算法代码

由于是经典算法,先看代码。

void MinSpanTree_Prim(Graph G)
{
int min, i, j, k;
int adjvex[MAXSIZE];//用于保存顶点下标
int lowcost[MAXSIZE];//保存顶点之间边的权值
lowcost[0] = 0;//初始化第一个权值为0,A加入生成树
adjvex[0]=0;//
for (i = 1; i {
lowcost[i] = G.edge[0][i];
adjvex[i] = 0;
}
for (i = 1; i {
min = INTMAX;
j = 1;
k = 0;
while (j {
if (lowcost[j] != 0 && &lowcost[j] {
min = lowcost[j];
k = j;
}
j++;
}
printf("边 %c到%c 已被纳入生成树\n", G.ver[adjvex[k]], G.ver[k]);
lowcost[k] = 0;
for (j = 1; j {
if (lowcost[j] != 0 && G.edge[k][j] {
lowcost[j] = G.edge[k][j];
adjvex[j] = k;
}
}
}
}


0x04.图解代码过程

下图是一个无向网图。顶点ABCDEFGHI分别是顶点表中的0~8号元素。



下面按照代码过程演示:

代码第4,5行创建了两个数组,一个adjvex,一个lowcost,其中adjvex用于保存顶点的下标,lowcost是整个普里姆算法的核心,用于保存相关边的权值,具体两个数组是如何存储的见下文。

代码第6,7行分别对两个数组的0号下标进行初始化,假设从A顶点开始,lowcost[0]=0,表示A已加入生成树,可以这样理解,lowcost中的某个数一旦被置为0,说明这个下标所在顶点表中的顶点被纳入生成树了,另外,adjvex[0]=0的含义是假设从A开始。

代码第8~12行的循环表示对lowcostadjvex数组赋值,lowcost所赋的值是与A有关的边的权值,adjvex所赋的值全部是0。

此时的lowcost为[0,8,32767,32767,32767,9,32767,32767,32767]

此时的adjvex全部为0.

代码第15~17行,将min设置为INTMAX,目的是找到最小权值;j设置为1,作为下一个循环的循环变量;k用来存储权值最小的顶点下标。

代码第18~26行的循环,目的是寻找lowcost中权值最小的值,和它对应的下标。

代码第27行,输出了生成树中的第一条边,此时的k表示的是lowcost中权值最小的边对应的顶点,而adjvex[k]则表示这条边的另一个顶点,在这也就是A

代码第28行,lowcost[k]=0,表示顶点k已加入生成树,对于locost0的值,以后都不进行操作。

代码第29~36行,再进行一次循环,此时的k=1,那么这次循环的目的就是查找邻接矩阵中顶点B所对应的边的权值,与之前lowcost中的值比较,若有值更小,则修改lowcost中的值,并将k存入adjvex

此时的lowcost[0,0,16,32767,32767,9,14,32767,10]

此时的adjvex[0,0,1,0,0,0,1,0,1]

第一遍大循环完成,此时的生成树如图所示(红色部分):

我们发现一次大循环的目的就是找到一条权值最小的边加入最小生成树。

再来看第二次大循环。

由于第一次小循环的目的是找到lowcost中权值最小的值和顶点,那么再经历第一次小循环后,k应该等于值为9对于的下标,也就是5,而adjvex[5]=0,所以此次加入的边应该是A--F。

第二次小循环的目的是改变lowcost中的值,那么再经历第二次小循环,此时k=5,循环结束后,lowcost应为[0,0,16,32767,24,0,14,32767,10],adjvex应为[0,0,1,0,5,0,1,0,1]。

此时的生成树为:

再来第三次大循环:

第一个小循环过后,找到最小权值对于的下标k为8,adjvex[8]=1,也就是说边B--I加入生成树。

第二个小循换过后,遍历k有关的边有没有比lowcost中的值小的,循环过后,lowcost应为[0,0,16,19,24,0,14,32767,0],adjvex应为[0,0,1,8,5,0,1,0,1].

此时的生成树为:

第四次大循环:

第一次小循环结束后,k=6,adjvex[6]=1,说明边B--G加入生成树。

第二次小循换结束后,lowcost为[0,0,16,19,24,0,0,17,0],adjvex为[0,0,1,8,5,0,1,6,1]

此时的生成树如图:

第五次大循环:

第一次小循换后,k=2,adjvex[2]=2,说明边B--C加入生成树。

第二次小循环后,lowcost为[0,0,0,19,24,0,0,17,0],adjvex为[0,0,1,8,5,0,1,6,1]。

此时的生成树如图:

第六次大循环:

第一次小循换后,k=7,afjvex[7]=6,说明边G--H加入生成树;

第二次小循换后,lowcost为[0,0,0,14,5,0,0,0,0],adjvex为[0,0,1,7,7,0,1,6,1]。

此时的生成树为:

第七次大循环:

第一次小循换后,k=5,adjvex[5]=7,说明边H--E加入生成树。

第二次小循换后,lowcost为[0,0,0,14,0,0,0,0,0],adjvex为[0,0,1,7,7,0,1,6,1]。

此时的生成树为:

第八次大循环:

第一次小循换,k=3,adjvex[3]=7,说明边H--D加入生成树。

第二次小循换,lowcost全为0,生成树已完成,无意义。

此时的生成树为:

代码到此执行完毕,最小生生成树构造完成。

一个大循环里面两个小循环,时间复杂度应为 O(e)=2e*e;

0x05.原理深究


1.关于lowcost,adjvex数组到底怎么理解。

lowcost是整个普里姆算法的核心部分,它的含义就是存储各边的权值,为0就代表,该下标所对应的顶点已加入生成树,不需要再考虑,这个权值应该这样理解,是对于已加入这棵生成树的顶点的所有边的最小权值,这个最小指的是和这个权值下标连成的边的最小权值。

例如,当第一次循环的时候,lowcost为[0,8,32767,32767,32767,9,32767,32767,32767]第一个0代表第一个顶点已加入生成树,第二个8代表,第二个顶点到已加入生成树的所有顶点之中权值最小的值,那么如何知道在第二个小循换中,改变了的话,是对于第几个顶点来说的,这个时候adjvex[k]的值就是对于第几个顶点而言,比如,此时的adjvex[2]=1,代表8这个值是对于第二个顶点来说,所有已加入生成树的顶点到这个顶点的最小权值。

lowcost每次在第一次小循换中,会找出一个最小值出来,并找到对应的顶点加入生成树。这个过程实际上就是找到所有顶点权值最小里面的最小。

lowcost中每有一个为0就是有一个顶点加入了生成树,当所有顶点都加入后,lowcost中的值都为0。

adjvex是存储相关顶点的下标的,那么这个顶点下标究竟指的是什么呢?我们可以看它是什么时候改变的呢,就是在第二个小循换才开始改变的,每次改变存的是一个权值对应的下标,所以可以这样理解,这个下标其实是与lowcost相同位置的值对应的,是表示lowcost里这个权值是对于第几个顶点而言的最小值,adjvex里的这个值和lowcost里那个数据对应的下标,就是这个权值所代表的边的两个端点。

具体两个数组是何用处,可以在不断的演练上述过程中发现规律。

2.对于第二个小循换而言,为什么是找相对位置的小值。

也就是说如何理解代码 if (lowcost[j] != 0 && G.edge[k][j]

这个原因其实就是lowcost中所存数据的意义,是对于每个特定顶点的权值最小值,而不是所有的最小值。

0x06.普里姆算法感悟

普里姆算法就是巧妙的利用了两个数组,对每一次循环都能找到一个最小值去加入生成树。每一次循环都巧妙的找出了已加入生成树的顶点的相应边对应的权值。

巧妙利用数组的下标的意义,能够使原本复杂的代码简便不少。

对数组中的元素赋以特定的值使之含义丰富。



本章结束。







推荐阅读
  • C语言判断正整数能否被整除的程序
    本文介绍了使用C语言编写的判断正整数能否被整除的程序,包括输入一个三位正整数,判断是否能被3整除且至少包含数字3的方法。同时还介绍了使用qsort函数进行快速排序的算法。 ... [详细]
  • Go语言实现堆排序的详细教程
    本文主要介绍了Go语言实现堆排序的详细教程,包括大根堆的定义和完全二叉树的概念。通过图解和算法描述,详细介绍了堆排序的实现过程。堆排序是一种效率很高的排序算法,时间复杂度为O(nlgn)。阅读本文大约需要15分钟。 ... [详细]
  • C语言常量与变量的深入理解及其影响
    本文深入讲解了C语言中常量与变量的概念及其深入实质,强调了对常量和变量的理解对于学习指针等后续内容的重要性。详细介绍了常量的分类和特点,以及变量的定义和分类。同时指出了常量和变量在程序中的作用及其对内存空间的影响,类似于const关键字的只读属性。此外,还提及了常量和变量在实际应用中可能出现的问题,如段错误和野指针。 ... [详细]
  • HashMap的相关问题及其底层数据结构和操作流程
    本文介绍了关于HashMap的相关问题,包括其底层数据结构、JDK1.7和JDK1.8的差异、红黑树的使用、扩容和树化的条件、退化为链表的情况、索引的计算方法、hashcode和hash()方法的作用、数组容量的选择、Put方法的流程以及并发问题下的操作。文章还提到了扩容死链和数据错乱的问题,并探讨了key的设计要求。对于对Java面试中的HashMap问题感兴趣的读者,本文将为您提供一些有用的技术和经验。 ... [详细]
  • HashMap的扩容知识详解
    本文详细介绍了HashMap的扩容知识,包括扩容的概述、扩容条件以及1.7版本中的扩容方法。通过学习本文,读者可以全面了解HashMap的扩容机制,提升对HashMap的理解和应用能力。 ... [详细]
  • 《2017年3月全国计算机等级考试二级C语言上机题库完全版》由会员分享,可在线阅读,更多相关《2017年3月全国计算机等级考试二级C语言上机题库完全版( ... [详细]
  • 文章目录题目:二叉搜索树中的两个节点被错误地交换。基本思想1:中序遍历题目:二叉搜索树中的两个节点被错误地交换。请在不改变其结构的情况下 ... [详细]
  • 本文由编程笔记#小编为大家整理,主要介绍了源码分析--ConcurrentHashMap与HashTable(JDK1.8)相关的知识,希望对你有一定的参考价值。  Concu ... [详细]
  • 动态规划算法的基本步骤及最长递增子序列问题详解
    本文详细介绍了动态规划算法的基本步骤,包括划分阶段、选择状态、决策和状态转移方程,并以最长递增子序列问题为例进行了详细解析。动态规划算法的有效性依赖于问题本身所具有的最优子结构性质和子问题重叠性质。通过将子问题的解保存在一个表中,在以后尽可能多地利用这些子问题的解,从而提高算法的效率。 ... [详细]
  • 本文探讨了C语言中指针的应用与价值,指针在C语言中具有灵活性和可变性,通过指针可以操作系统内存和控制外部I/O端口。文章介绍了指针变量和指针的指向变量的含义和用法,以及判断变量数据类型和指向变量或成员变量的类型的方法。还讨论了指针访问数组元素和下标法数组元素的等价关系,以及指针作为函数参数可以改变主调函数变量的值的特点。此外,文章还提到了指针在动态存储分配、链表创建和相关操作中的应用,以及类成员指针与外部变量的区分方法。通过本文的阐述,读者可以更好地理解和应用C语言中的指针。 ... [详细]
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • 本文讨论了Kotlin中扩展函数的一些惯用用法以及其合理性。作者认为在某些情况下,定义扩展函数没有意义,但官方的编码约定支持这种方式。文章还介绍了在类之外定义扩展函数的具体用法,并讨论了避免使用扩展函数的边缘情况。作者提出了对于扩展函数的合理性的质疑,并给出了自己的反驳。最后,文章强调了在编写Kotlin代码时可以自由地使用扩展函数的重要性。 ... [详细]
  • 欢乐的票圈重构之旅——RecyclerView的头尾布局增加
    项目重构的Git地址:https:github.comrazerdpFriendCircletreemain-dev项目同步更新的文集:http:www.jianshu.comno ... [详细]
  • 本文介绍了使用哈夫曼树实现文件压缩和解压的方法。首先对数据结构课程设计中的代码进行了分析,包括使用时间调用、常量定义和统计文件中各个字符时相关的结构体。然后讨论了哈夫曼树的实现原理和算法。最后介绍了文件压缩和解压的具体步骤,包括字符统计、构建哈夫曼树、生成编码表、编码和解码过程。通过实例演示了文件压缩和解压的效果。本文的内容对于理解哈夫曼树的实现原理和应用具有一定的参考价值。 ... [详细]
author-avatar
9位特权QQ号码连号
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有