最短路径概述
本文参考了《算法导论》,将对最短路径问题做一个概述,以便大家在学习后续相关最短路算法能够如鱼得水。
一.什么是最短路径
来看这样一个问题,老方
想去北京旅游,由于她买不起飞机票,她只能通过汽车周转多次去往北京,已知每条道路都有一个相关花费 worth
,记 W = worth[1] + worth[2] + .... + worth[n]
,求W的最小值。
对于这样的问题,我们的朴素思想是枚举,枚举每一组可能通往北京的路径组合,分别求出他们的总价值,最后遍历一次获得最小价值。这种算法的正确性显然,但太慢了,会枚举很多没有用的组合,比如从莫斯科周转,就很蠢。
我们需要先明确一个问题,最短路径问题不仅针对距离,还可以用来计算其他一切可以随着路径长度增加而线性积累的数量以及我们想要最小化的数量。
二.最短路径问题的类型
最短路径问题分为:单结点的最短路径问题,单目的地的最短路径问题,所有节点的最短路径问题。
单节点的最短路径问题几乎等价于单目的地的最短路径问题,因为单目的地的最短路径问题可以通过反向建边的方法转换成单节点的最短路径问题。
现实生活中,边权 即 worth
很少有负值,然而,一些最短路算法,却无法计算含有负边权的图的最短路问题,比如dijstra算法。同时,他们也无法计算存在环路的图,因为只要将环路从路径上删除就可以得到一个源节点与终节点不变的一条权重更小的路径。
三.两个重要技术
我们用 v.pre
来表示当前节点 v
的前驱节点 pre
,用 v.d
is 来表示当前节点 v
到根节点(起点)的最短路径 dis
, 那么我们可以写出这样一个初始化伪代码。
for each vertex v
v.dis = 0x7fffffff /*0x7fffffff是int类型所能存储的最大值*/
v.pre = Null /*表示每个点一开始都没有前驱节点*/
s.dis = 0 /*起点到起点的距离 == 0*/
在初始化结束后,我们可以进行松弛操作。
/*我们把一条边的(u, v)松弛过程描述为:如果s-->v的最短路径距离 加上 worth[u-->v] < s-->v的最短路径距离,那么对其进行更新。*/
for each edge(u, v)
if v.dis > u.dis + worth[u-->v]
v.dis = u.dis + worth[u-->v]
v.pre = u
/*为什么叫做松弛操作,因为这个限制条件即v.dis <= u.dis + worth[u-->v] 恒成立,所以称它是"松弛"的*/
请注意!这里的给出的代码均为伪代码,其作用在使大家更好的理解,真正的c++实现在后文。
细心的同学可以发现,我们在循环过程中需要枚举边,即我们需要维持一个良好的可以存边的数据结构。