最小生成树之Prim(普里姆)算法

最小生成树之Prim(普里姆)算法Prim 算法是解决最小生成树的经典算法之一 本博客以个人的理解简略地对 Prim 算法进行介绍以及解剖

注明:本博客参考《数据结构教程》第4版

(一)知识准备

在开始学习Prim算法之前,我们需要对图有一定的了解,且知道图的存储方式(本博客基于无向图邻接矩阵的知识),同时我们要了解什么是生成树?一个连通图的生成树是该连通图的一个极小连通子图,它是含有图的全部顶点,但只有构成一棵树的(n-1)条边,而最小生成树则是在生成树的基础上,要求树的(n-1)条边的权值之和是最小的。由此可以总结构造最小生成树的要求有:(1)必须只使用该图中的边来构造最小生成树;(2)必须使用且仅使用(n-1)条边来连接图中的n个顶点;(3)不能使用产生回路的边;(4)要求树的(n-1)条边的权值之和是最小的。

(二)算法初识

接着开始我们的Prim算法的学习!首先我们先来认识下Prim算法的基础认识(后面我们会更加严谨的给出其定义与推算过程):通过选点来构造最小生成树,每次(第一次
可随意挑选)从中挑选当前最适合的(即选俩点的权值最小的,使构造的当前生成树最小)点来构造最小生成树。

(三)算法实例演示

首先让我们来看一个example。如下图所示,图a是一个连通图(右图是图a对应的邻接矩阵,假设图中的边的权值大于0),我们现在基于该图来演示Prim算法的过程。

最小生成树之Prim(普里姆)算法 最小生成树之Prim(普里姆)算法

我们选择一个起点,然后在与起点相连且未被选的节点中选择一个权值最小的节点,将该节点与其相连边添加入生成树。假设起点是0节点,与0节点相连且未被选的节点是{1,2,3},分别对应的权值是{
6,1,5}可见当前最小的权值1,权值最小的节点就是2节点,所以将2节点和0-2的边添加入生成树,如图b所示。

最小生成树之Prim(普里姆)算法 

接着我们在与已选节点相连且未被选的节点中选择一个权值最小的节点,将该节点与其相连边添加入生成树。当前已选节点是0,2节点,与已选节点相连且未被选的节点有{1,3,4,5},分别对应的权值是{(6,5),(5,5),6,4,}可见当前最小的权值4,权值最小的节点就是5节点,所以将5节点和2-5的边添加入生成树,如图c所示。(其实在编程时,我们只需记录与更新当前较小的那个权值,如与{1,3,4,5}对应的权值我们只需记录{5,5,6,4},当然我们也需利用了另一个数组来加以区别当前权值对应的连接点,如当前权值{5,5,6,4}所对应的连接点就是{2,0,2,2})

最小生成树之Prim(普里姆)算法  

接着我们继续在与已选节点相连且未被选的节点中选择一个权值最小的节点,将该节点与其相连边添加入生成树。当前已选节点是0,2,5节点,与已选节点相连且未被选的节点有{1,3,4},分别对应的权值是{(6,5),(2,5,5),(6,6),}(其实当前我们可只记录{5,2,6},同时记录其对应的连接点分别是{2,5,2}),可见当前最小的权值2,权值最小的节点就是3节点,所以将3节点和5-3的边添加入生成树,如图d所示。

最小生成树之Prim(普里姆)算法 

接着我们依照上一次的步骤继续在与已选节点相连且未被选的节点中选择一个权值最小的节点,将该节点与其相连边添加入生成树。如图e,f所示。最终图f就是我们通过Prim算法得到的最小生成树了。

最小生成树之Prim(普里姆)算法 最小生成树之Prim(普里姆)算法

(四)算法概念

现在我们给出Prim的严谨概念:Prim算法是一种构造性算法。假设G=(V,E)是一个具有n个顶点的带权连通无向图,T=(U,TE)是G的最小生成树,其中U是T的顶点集,TE是T的边集,则由G构造从起始顶点v出发的最小生成树T的步骤如下:

(1)初始化U={v},以v到其他顶点的所有边为候选边;

(2)重复以下步骤(n-1)次,使得其他(n-1)个顶点被加入到U中:

1.从侯选边中挑选权值最小的边加入TE,设该边在V-U中的顶点是k,将k加入U中;

2.考察当前V-U中所有顶点j,修改侯选边,若边(k,j)的权值小于原来和顶点j关联的侯选边,则用边(k,j)取代后者作为侯选边

现在我们可以来编程实现Prim算法啦!

(五)编程实现

首先,在动手编程之前我们需要了解我们考虑实现Prim算法的一些相关的东西。

第一个考虑的便是传入的参数,第一个参数就是无向图的信息(这里我们用邻接矩阵MGraph来存储),第二个参数是起点。下面给出邻接矩阵的结构体。

//邻接矩阵的数据类型 #define MAXV 50 //最大顶点数 typedef struct { int no;//顶点编号 //InfoType info;//顶点的其他信息 }VertexType;//顶点类型 typedef struct { int edges[MAXV][MAXV];//邻接矩阵的边数组 int n, e;//顶点数,边数 VertexType vexs[MAXV];//存放顶点信息 }MGraph;//图邻接矩阵类型 #define INF 32767//表示无穷大

下面来介绍实现Prim算法的一些重要的变量或结构。

为了便于选中当前权值最小的边的节点,需要建立两个数组closest和lowcost,对于某个未选中的节点j,lowcost[j]存储的是节点j与当前已选节点相连的最小权值(lowcost[j]==0表示节点j已被选),closest[j]存储lowcost[j]对应的连接点,如下图所示。

最小生成树之Prim(普里姆)算法

按照上面例子(即按图a得到最小生成树),其求解过程中lowcost数组和closest数组的变化,如下图所示(希望读者结合下面的程序代码来分析这两个数组)。

最小生成树之Prim(普里姆)算法

现在我们可以动手编程了!代码如下:

void Prim(MGraph g, int v)//普利姆算法(参数:邻接矩阵,起点(即第一个生成的点,可随便取)) { int lowcost[MAXV], closest[MAXV], i, min, j, k; /*初始化lowcost数组,closest数组(即从起点开始设置lowcost数组,closest数组相应的值,以便后续生成使用)*/ for (i = 0; i < g.n; i++)//赋初值,即将closest数组都赋为第一个节点v,lowcost数组赋为第一个节点v到各节点的权重 { closest[i] = v; lowcost[i] = g.edges[v][i];//g.edges[v][i]的值指的是节点v到i节点的权重 } /开始生成其他的节点*/ for (i = 1; i < g.n; i++)//接下来找剩下的n-1个节点(g.n是图的节点个数) { /*找到一个节点,该节点到已选节点中的某一个节点的权值是当前最小的*/ min = INF;//INF表示正无穷(每查找一个节点,min都会重新更新为INF,以便获取当前最小权重的节点) for (j = 0; j < g.n; j++)//遍历所有节点 { if (lowcost[j] != 0 && lowcost[j] < min)//若该节点还未被选且权值小于之前遍历所得到的最小值 { min = lowcost[j];//更新min的值 k = j;//记录当前最小权重的节点的编号 } } /输出被连接节点与连接节点,以及它们的权值*/ printf("边(%d,%d)权为:%d\n", closest[k], k, min); /*更新lowcost数组,closest数组,以便生成下一个节点/ lowcost[k] = 0;//表明k节点已被选了(作标记) //选中一个节点完成连接之后,作数组相应的调整 for (j = 0; j < g.n; j++)//遍历所有节点 { /* if语句条件的说明: * (1)g.edges[k][j] != 0是指k!=j,即跳过自身的节点 * (2)g.edges[k][j]是指刚被选的节点k到节点j的权重,lowcost[j]是指之前遍历的所有节点与j节点的最小权重。若g.edges[k][j] < lowcost[j],则说明当前刚被选的节点k与节点j之间存在更小的权重,则需要更新 * (3)有人会问:为什么只跳过掉自身的节点(即k==j),而不跳过所有的已选节点?当然我们可以在if语句条件中增加跳过所有的已选节点的条件(即lowcost[j] == 0),而在本程序中我们只跳过了自身的节点?(注意:我们假设图中的边的权值大于0)但其实不是,g.edges[k][j] < lowcost[j]条件已包含跳过所有的已选节点,原因是在邻接矩阵中权值为0是最小的,即g.edges[k][j]>=0,而已选节点满足lowcost[j] == 0,则已选节点j是不满足g.edges[k][j] < lowcost[j],则会被跳过 */ if (g.edges[k][j] != 0 && g.edges[k][j] < lowcost[j]) { //更新lowcost数组,closest数组 lowcost[j] = g.edges[k][j];//更新权重,使其当前最小 closest[j] = k;//进入到该if语句里,说明刚选的节点k与当前节点j有更小的权重,则closest[j]的被连接节点需作修改为k } } } }

Prim算法中有两重for循环,所以时间复杂度为O(n^2),其中n为图的顶点个数。

本人是一个喜欢算法的新手,本博客以自己的理解简要的介绍了Prim算法,若本博客有错误需要修改的或对排版风格有要改进的等建议,请留言或发邮件给我。写博客是一个相互学习的过程,期待收到您的建议!记住,不要沮丧,好好学习,天天向上!共勉!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/232165.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • idea tomcat8.5乱码_启动tomcat乱码

    idea tomcat8.5乱码_启动tomcat乱码打开tomcat安装目录conf/logging.properties,将所有的GBK内容改为UTF-8修改IDEA配置属性HELP->EditCustomVMOptions->添加一行->重启IDEA-Dfile.encoding=UTF-8效果图帮助到你的话,点个赞,鼓励一下,欢迎加入我的置顶博客设置的技术交流群。…

    2022年10月17日
    1
  • Ngnix 搭建视频直播服务器[通俗易懂]

    Ngnix 搭建视频直播服务器[通俗易懂]受疫情推迟开学影响,这段时间全国如火如荼推广网络教学,前段时间搭建了edx慕课平台,但还缺点什么,就是网络直播教学,花一天时间,搭建成功,记录备用。1.基本技术路线其中,服务器采用nginx+nginx-rtmp-module,推流采用OBS-Studio,拉流采用html5网页播放2.直播服务器安装环境centos7,没有安装桌面图形界面,server版y…

    2022年4月30日
    83
  • 函数指针

    函数指针前言:先看两个基础,函数指针和extern关键字,然后由一个具体的例子,具体使用下函数指针。一、基础函数指针:即指向函数的指针,本质还是一个指针。函数指针的声明:返回值类型(*指针变量名)

    2022年7月4日
    20
  • Oracle数据库常用Sql语句大全

    Oracle数据库常用Sql语句大全最简单的就是查询:select语句数据库操作语言DML:update、insert、delete等数据库定义语言DDL:create、drop、alter等等oracle取前几条数据语句sqlserver中可以用topn的方法,oracle中用rownum,但如果只用rownum会随机取数据,如果想按一定顺序取前几条数据则可这样写:select*from(select列from表where条件orderby列desc)whererownum<>sel

    2022年5月12日
    42
  • oralce入门学习[通俗易懂]

    oralce入门学习[通俗易懂]oracle的认识sql数据库语言关键字distinct关键字null连接符||比较运算符排序单行函数字符函数数值函数日期函数转换函数通用函数条件表达式多行函数

    2022年7月2日
    29
  • JDK1.8下载、安装和环境配置教程

    JDK1.8下载、安装和环境配置教程一、下载安装包1.JDK1.8百度云下载路径:链接:https://pan.baidu.com/s/1ozCGy53AIeQIHWL6s9oAbw提取码:04lf网盘放的是jdk1.8版本中的1.8.0_152的版本2.如果大家想下载别的版本,可以去官网:www.oracle.com下载,进入官网页面,然后点击Downloads。…

    2022年6月12日
    32

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号