前言
之前的几篇文章我们描述了图作为一种容器的基本属性和作为树形结构的基本算法。
在探讨Dijkstra算法之前,请准备好笔和纸,我们一起来画一画该算法的解决过程,否则你将很难理解该算法。
先行知识
问题:
求一个带权无向图两个顶点b,e的最短路径p
P {b - e} = P {b - … k - … - s … - e},k和s为路径p上的两个顶点
如果P为最短路径,那么这条路径的部分路径P {k - s}一定也是k到s的最短路径证明:反证法:
我们知道 P {b - e} = P{b - k} + P{k - s} + P{s - e}
如果存在P’{k - s} < P {k - s}
那么一定存在 P’{b - e} = P{b - k} + P’{k - s} + P{s - e} < P {b - e}
而P {b - e}是最短的,所以P’ {b - e}不存在,假设不成立
Dijkstra就是基于这样的理论,我们根据最优路径局部也最优,可以一步一步地迭代计算出最短路径。
百度一下,你会发现各种版本的Dijkstra,他们实现方法大不相同,但是其实算法核心都是一样的,我们这里通过构建最短路径树得到结果
算法过程
首先画出一个无向图,权值大概标记的,不用太较真:
运行Dijsktra(1 ,8)
表达一颗树是不容易的,但是幸好我们只需要树的很少一部分功能,所以此处和DFS中一样,使用父节点标记数组来表示树,但是如果你不能理解这个,你可以依然画一棵树来表示下面的详细过程。
index | parent |
---|---|
0 | 0 |
1 | 0 |
2 | 1 |
3 | 1 |
这个数组可以表示一颗具有三个节点的树,根节点是1,两个子节点2和3:
1 / \ 2 3
详细过程
OK,我们可以开始算法了
从源顶点1开始扩展,数组中零顶点为0(表示0顶点无意义),其余也为0,表示无法访问
数组为:
{0,0,0,0,0,0,0,0,0,0}最优顶点为2,总权值dist{1-2} = 1,我们从2的父顶点为1,插入数组
{0,0,1,0,…0}
由于顶点1还有其他连接顶点,我们标记顶点1为边界顶点,存到边界顶点数组中(动态数组)(就是当前树的所有叶子)现在源顶点为2了,2有两个顶点,3和5(1已经访问不能重复访问),最优为3,此时我们访问所有的边界顶点(现在只有顶点1),看是否还有更优解。
dist{1-2-3} = 1.6
dist{1-3} = 1.5<1.6
所以3的父节点是1
{0,0,1,1,0,…0}
这一步骤之后,1没有剩余顶点了,移出边界数组,加入2现在源顶点为3了,3的三个顶点等权,我们随机取(在代码中按照List的第一个来取),我们取4,由于现在的边界顶点只有2,没有路径通向4,我们视为权值为无穷,所以插入4的父顶点3,把3加入边界数组。
{0,0,1,1,3,…}源顶点为4,同理5的父顶点为2,
{0,0,1,1,3,2,…}省略,当我们访问完顶点8时,我们发现,没有接下来的顶点了,此时数组为:
{0,0,1,1,3,2,0,0,5,0}接下来,我们看边界顶点数组,现在是这样的{3,5},顺序访问3,访问6,然后重复这步访问7,3没有其他顶点了,移出边界数组。
{0,0,1,1,3,2,3,3,5,0}重复上步,直到移出所有边界顶点,所以判断循环结束的条件是:
1、边界顶点数组为空
2、所有顶点都标记为visited
这两个条件其实是等价的,我们在一开始把顶点1加入边界数组,然后判断条件1即可。如果你是画树的,最后我们会得到这样的一个树:
总结
所有步骤如上所示,如果你还不是特别理解Dijkstra,那么拿上笔,重复我描述的步骤。
我将在下一篇文章讲述代码如何抽象该算法,咱们下次见。;-)