1. 前言
权重图中的最短路径有两种,多源最短路径和单源最短路径。多源指任意点之间的最短路径。单源最短路径为求解从某一点出到到任意点之间的最短路径。多源、单源本质是相通的,可统称为图论的最短路径算法,最短路径算法较多:
Floyd-Warshall
算法。也称为插点法,是一种利用动态规划思想寻找权重图中多源点之间[最短路径的算法,与Dijkstra
算法类似。该算法名称以创始人之一、1978年图灵奖获得者、斯坦福大学计算机科学系教授[罗伯特·弗洛伊德命名。Bellman_ford
算法。贝尔曼-福特算法取自于创始人理查德.贝尔曼和莱斯特.福特,暴力穷举法,算法效率较低。但是,能解决的问题范围较大,如负权问题。SPFA
算法。Bellman-Ford
的队列优化版,本质一样。Dijkstra
算法。迪杰斯特拉算法(Diikstra)
是由荷兰计算机科学家狄克斯特拉于1959 年提出的,因此又叫狄克斯特拉算法。经典算法,本质是贪心算法。
下面逐一介绍这三种算法。
2. Floyd-Warshall
权重图中,任意两点之间的路径可能存在多条,但是最短的是哪条?
如果你现在想从城市1
去城市2
,固然是想找一条最短路径的,找出量短路径,意味着省时、省钱、省精力……
本质上就是做选择题。面对这样的情况,你首先要做的便是绘制地图,描绘出与城市1
和城市2
直接、间接相邻的城市。并初始化城市与城市之间的已知权重。如下图所示。
先得到两点直接相连时的路径。如上图的1
和2
两点之间,不存在直接相连的边,初始可认为是很大、很大……
使用矩阵描述为 graph[1][2]=INF
。INF
是一个自定义的常量,存储一个较大的值。
现实生活中,当直接不能到达,或直接到达的成本很高时,会考虑经过一个中转站。这样可能会降低成本。到底经过那个中转站能降低成本。这个只能逐一试试。可以把除了1
和2
之外的所有节点做为中转站,然后比较是否比之前的路径更短。比如,在1
和2
之间插入3
号节点。
这样你的旅行路就分割成了两段,一段是从1
到3
、一段是从3
到2
。如下图,标注红色的为新路线。1->3
的权重为4
、3->2
的权重为7
。累加权重为11
。显然,比之前的INF
要短的,想必你是不会犹豫地选择这条新路线。
这时在你的脑海中,应该使用如下的式子获取到1->2
的新的权重。
//因为走 1->3后、再走`3->2`的路线要比之前路径短
// 必然要选择新的路线
if(graph[1][3]+graph[3][2]<graph[1][2])
graph[1][2]=graph[1][3]+graph[3][2];
原理很简单,这时你应该会思考,可能经过2
个或多个或其它中转站比经过1
个中转站更省成本。是的,现在还不是最终的选择。
每一次更新后,你需要继续试着添加其它节点做为中转站。检查是否更短,如果更短,继续更新,如果更远,就不用更新。如可以试着把4
号点做为中转站。这时路线权重变成 graph[1][4]+graph[4][2]=15
,并没有比之前经3
为中转站中的值更小,以4
为中转站这个方案要淘汰掉。
选择5
做为中转站,你会发现graph[1][5]+graph[5][2]=11
。既然发现了更短的路径,更新邻接矩阵中graph[1][2]
的值。这时你应该有所感悟,下图中的邻接矩阵不就是一张动态规划表吗?
**Tips:**在不断的插入节点,得到新路线后,节点之间的权重值会发生变化。如果需要保留原始图中的节点之间的信息,可以再创建独立的动态规划表。
在1和2
之间把其它节点都插入了,然后得到了现阶段的最短权得和10
。是不是就是最终的结果呢?
如果你善于观察,从1->3
、然后3->5
、再5->2
,其权重和为7
。这条路径才是1->2
之间的最短路径。也就是说,经过多个中转站也许比只经过一个中转站会让路径更短。
现在的问题是,我们直接朝目标而来,其实没有考虑,你所经过的中间路径也有可能有更短的。如现阶段,我们认为的1->5
然后5->2
是最短的,但是,是否忽视了1->5
也许也存在最短路径。只有最短加最短才能得到真正的最短。
其实,这也符合动态规划思想,必须存在最优子结构吗!最终问题必然是前面的的子问题一步一步推导出来的。所以,Floyd
算法告诉我们,必须更新任意两点之间的路径,才能得到你希望的两点之间的最短路径。
也就是说,当分别插入1、2、3、4、5
号节点时,对其它任意两点的距离影响都要记录在案。比如,在插入1
号节点时,对其它任意两点的影响如下图所示。对3-4
和4-5
之间的影响是较大。
选择3
号点做作插入点,检查其它任意两点之间经过3
号点是否能让路线变得更短。发现,1-5
之间的距离被缩短了。
当选择5
号点做为插入点,计算1-2
点间的距离时,此时会发现1->3->5->2
才是最终的最短距离。
理解Floyd
算法的关键,动态规划本质是穷举算法,只有当所有可能都计算一次,才能得到最终结果。
编码实现:
#include <iostream>
using namespace std;
//图
int gra