知识框架

知识点详解
No.1
最小生成树
首先我们来了解一下最小生成树的概念:对于一个带权连通无向图G=(V, E),生成树不同,每棵树的权(即树中所有边上的权值之和)也可能不同。设R为G的所有生成树的集合,若T为9中边的权值之和最小的那棵生成树,则T称为G的最小生成树(MST)。
构成最小生成树有许多算法,大多数算法都利用到了它的一个性质:在一个带权连通无向图中,若有一个最小权值的边,则该一定存在包含该边的最小生成树。基于这条性质,就有了我们接下来介绍的基于贪心算法的Prim算法以及Kruskal算法。对于这两种算法,同学们务必要掌握其本质含义与基本思想,并能手动模拟出算法的实现步骤。
✔Prim算法
『思想』
设图的所有顶点的集合为V。首先从图中的一个起点a开始,把a加入T集合,然后,寻找从与a有关联的边中,权重最小的那条边并且该边的终点b在顶点集合(V-T)中,我们也把b加入到集合T中,并且输出边(a,b)的信息,这样我们的集合T就有:{a,b}。
接着,我们寻找与a关联和b关联的边中,权重最小的那条边并且该边的终点在集合:(V-T)中,我们把c加入到集合U中,并且输出对应的那条边的信息,这样我们的集合U就有:{a,b,c}这三个元素了,依次类推,直到所有顶点都加入到了集合T,就完成了构建最小生成树的过程。
对于下图,我们来模拟Prim算法构造最小生成树的实现过程:

第一步:选择图中权值最小的边(V1,V2),将V1、V2加入顶点集合T

第二步:选择与顶点集合中距离最近的顶点,(V3,V6),将V6加入顶点集合T

第三步:重复第二步的步骤,选择(V6,V4),将顶点V4加入顶点集合T

第四步:选择(V3,V2),将顶点V2加入顶点集合T

第五步:选择(V2,V5),将顶点V5加入顶点集合T,最小生成树构建完成。

Tips:由于Prim算法的时间复杂度为O(|V²|),不依赖于E,所以它适用于求解边稠密的图的最小生成树。

✔Kruskal算法
它是一种不同于Prim算法的一种按权值递增的次序选择合适的边来构造最小生成树的方法。
『思想』
①将图中的所有边都去掉
②将边按权值从小到大的顺序添加到图中,保证添加的过程中不会形成环
③重复上一步直到连接所有顶点,此时就生成了最小生成树
对于下图,我们来模拟Kruskal算法构造最小生成树的实现过程:

第一步:选择图中权值最小的边(V1,V3)

第二步:选择图中权值次小的边(V6,V4)

第三步:选择图中权值第三小的边(V2,V5)

第四步:选择图中权值第四小的边(V3,V6)

第五步:选择图中权值第五小的边(V2,V3),此时图中的所有顶点都在一个连通分量上,构造完成。

Tips:与Prim算法不同,Kruskal算法适合于边稀疏而顶点较多的图。
No.2
最短路径
当图为带权图时,把从一个顶点a到一个顶点b的一条路径(可能不止一条)所经过边的权值之和,定义为该路径的带权路径长度,而最短路径就是带权路径长度最短的那条路径。
求解最短路径一般有两类:
①求单源最短路径:Dijkstra算法
②求每对顶点之间的最短路径:Floyd算法
✔Dijkstra算法
Dijkstra是用来求解单源最短路径的,而单源最短路径是什么呢?
从一个顶点出发,Dijkstra算法只能求一个顶点到其他点的最短距离而不能是任意两点。
和之前提到的BFS求的最短路径有什么区别吗?
BFS求最短路径的图的边是不带权值或权值相等的,与其说它求的是路径,不如说它求的是次数。
执行Dijkstra算法需要一定的前提:连通图的边的权值不能为负数。也就是Dijksra处理的都是带正权值的连通图。
算法实现具体步骤:
①初始化,设置一个集合S记录已求得的最短路径的顶点,将源点V0先加入到集合S中。当集合S每并入一个新的顶点时,都要修改源点到集合V-S中顶点当前最短路径值的大小。
设置一个辅助数组dist[],记录从源点到其各顶点当前的最短路径,它的初态为arcs[v0][i],若从 V0到vi有弧,则 dist[i]为弧边上的权值,否则为∞。邻接矩阵arcs表示带权有向图,arc[i][j]表示有向边的权值,若不存在有向边则arcs[i][j]=∞。dist[i]的初始值为arcs[0][i]。
②从顶点集合V-S中选出Vj,使得dist[j]=Min{dist[i]|Vi∈V-S},此时Vj就是从顶点Vi出发的一条最短路径的终点。将顶点j并入集合S中。
③修改从V0出发到集合V-S上任一顶点Vk可达的最短路径的长度,若dist[j]+arcs[j][k]
④重复步骤 2、3,直到 V中所有顶点都被包含在集合S中。
我们来模拟一下Dijkstra算法求单源最短路径的过程:
对于下图,我们要求顶点1到其他各个顶点的单源最短路径。

第一轮:1->5,路径距离为5。
第二轮:1->5->4,路径距离为7。
第三轮:1->5->2,路径距离为8。
第四轮:1->5->2->3,路径距离为9。
图中所有顶点的单源最短路径均已求得,算法结束。
具体求解过程如下图所示:

其实Dijkstra和Prim算法非常相像,只是由于问题不同,实现过程的计算内容不同,前者计算路径长度,后者比较边的长短。

✔Floyd算法
Floyd算法为动态规划算法。它可以求得任意两点间的最短路径,可以处理有向图或Dijkstra算法不能处理的负权值的最短路径问题。
我们来看一下算法实现步骤:
首先定义一个n阶方阵序列A-1,A0,....,An-1,其中A-1[i][j]=arcs[i][j],Ak[i][j]=Min{Ak-1[i][j],Ak-1[i][k]+Ak-1[k][j]},k=0,1,...,n-1。
上式中,A0[i][j]为顶点Vi到Vj而中间顶点为0的最短路径长度。Ak[i][j]为Vi到Vj,中间顶点的序号不大于k的最短路径的长度。Floyd的本质上就是一个迭代的过程,每经过一次迭代,在从Vi到Vj的最短路径上就多考虑了一个顶点,经过多轮迭代后就可求得任意两点之间的最短路径长度了。
相信大家对Floyd算法已经有了初步的了解了,现在我们来实践一下。
对于下图,使用Floyd算法求任意两点间的最短路径。

①初始化方阵A-1[i][j] = arcs[i][j]。
②将V0作为中间顶点,对于所有顶点对{i,j},如果有A-1[i][j]> A -1[i][0] +A-1[0][j],则将A-1[i][j]更新为A-1[i][0] +A-1[0][j]。有A-1[2][1]>A-1[2][0]+A-1[0][1]=11,更新A-1[2][1]=11,更新后的方阵标记为A0。
③将V1作为中间顶点,继续检测全部顶点对{i,j}。有A0[0][2]> A0[0][1]+ A0[1][2]= 10,更新A°[0][2]=10,更新后的方阵标记为A1。
④将V2作为中间顶点,继续检测全部顶点对{i,j}。有A'[1][0]> A'[1][2]+ A'[2][0]=9,更新A'[1][0]=9,更新后的方阵标记为A2。此时A2中保存的就是任意顶点对的最短路径长度。
Floyd算法的执行过程如下图所示:

对于以上几种图的应用,大家只需能够自己手动模拟算法的实现流程。对于具体的算法实现代码,大多数学校都不考察,感兴趣的同学可以继续深入了解。
下面我们来看几道关于图的应用的例题。

根据Dijkstra算法思路解题,答案:B

提示:生成树的树形可能不唯一
答案:A
往期推荐
带你解读图的邻接矩阵和邻接表的变换
链表递归题型大解密
带你解读图的两种遍历方式
带你搞定考研二叉树算法(上)
带你搞定考研二叉树算法(下)
划重点!学姐带你掌握信号量机制
