一、概述
最短路径问题是图论研究中的一个经典算法问题, 如图,我们把边带有权值的图称为带权图。边的权值可以理解为两点之间的距离。一张图中任意两点间会有不同的路径相连。最短路径就是指连接两点的这些路径中最短的一条。
算法具体的形式包括:
确定起点的最短路径问题 - 即已知起始结点,求最短路径的问题。
确定终点的最短路径问题 - 与确定起点的问题相反,该问题是已知终结结点,求最短路径的问题。在无向图中该问题与确定起点的问题完全等同,在有向图中该问题等同于把所有路径方向反转的确定起点的问题。
确定起点终点的最短路径问题 - 即已知起点和终点,求两结点之间的最短路径。
全局最短路径问题 - 求图中所有的最短路径。
用于解决最短路径问题的算法被称做“最短路径算法”, 有时被简称作“路径算法”。
最常用的路径算法有:Floyd-Warshall算法、Dijkstra算法、Bellman-Ford算法、SPFA算法。
注:边的权值可以为负,当出现负权边时有些算法不适用。
二、全局最短路-Floyd算法
1、基本框架
可用于计算图中任意两点间的最短路径,适用于出现负权边的情况,时间复杂度为O(N^3)。
基本思想如下:从任意节点A到任意节点B的最短路径不外乎2种可能,1是直接从A到B,2是从A经过若干个节点到B。假设dist(AB)为节点A到节点B的最短路径的距离,对于每一个节点K,我们检查dist(AK) + dist(KB) < dist(AB)是否成立,如果成立,证明从A到K再到B的路径比A直接到B的路径短,便可设置 dist(AB) = dist(AK) + dist(KB),当遍历完所有节点K,dist(AB)中记录的便是A到B的最短路径的距离。
代码不在这里展示了,我们只需在i到j间枚举1-k个点,dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j])即可解出
2、Dijkstra算法
用于计算从一个点到其他所有点的最短路径(单源最短路径算法)。时间复杂度为O(N2),不能处理存在负权边的情况。
算法理解:
把图上的点分成两类:一类是已确定最短路径的点,称为白点;一类是未确定最短路径的点,称为蓝点。如果要求出一个点的最短路径,就是把这个点由蓝点变为白点。从起点到蓝点的最短路径上的中转点在这个时刻只能是白点。
如图,求点1到其余点的最短路径。
算法开始时,起点dis[1]=0,其余dis[i]= MaxInt;trv[i]=0;
一共5个点,通过5次循环确定一个最短路径的点。并维护未访问点的路径长度。
第一次,dis数组中未被访问过的点dis[1]最小,将1标记为白色(trv[1]=1);通过判断dis[1]+tu[1][k]<dis[k]确定dis[2]=2,dis[3]=4,dis[4]=7。(dis[1]=0,dis[5]= MaxInt)
第二次,dis数组中未被访问过的点dis[2]最小,将2标记为白色(trv[2]=1);通过判断dis[2]+tu[2][k]<dis[k]确定dis[3]=3,dis[5]=4。(dis[1]=0,dis[2]=2,dis[4]=7)
第三次,dis数组中未被访问过的点dis[3]最小,将3标记为白色(trv[3]=1);通过判断dis[3]+tu[3][k]<dis[k]确定dis[4]=4。(dis[1]=0,dis[2]=2,dis[3]=3,dis[5]=4)
第四次,dis数组中未被访问过的点dis[4]最小,将4标记为白色(trv[4]=1);判断dis[4]+tu[4][k]<dis[4]与之连接的点,无修改。(dis[1]=0,dis[2]=2,dis[3]=3,dis[4]=4 dis[5]=4)
第五次,dis数组中未被访问过的点dis[5]最小,将5标记为白色(trv[5]=1);通过判断dis[5]+tu[5][k]<dis[5] 与之连接的点,无修改。(dis[1]=0,dis[2]=2,dis[3]=3,dis[4]=4,dis[5]=4)
3、Bellman-Ford算法
用于计算从一个点到其他所有点的最短路径(单源最短路径算法), 能处理存在负权边的情况,但无法处理存在负权回路的情况。时间复杂度为O(NE),N为顶点数E为边数。
算法理解:
一开始认为起点是白点(dis[s]=0),每一次都枚举所有的边,必然会有一些边。连接着白点和蓝点。因此每次都能用所有的白点去修改所有的蓝点,每次循环也必然会有至少一个蓝点变成白点。
这个不要掌握,因为它有一个优化,没错就是最短路中,最重要的算法 SPFA
4、Bellman-Ford算法的优化- SPFA算法
SPFA是Bellman-Ford算法的队列实现,减少了不必要的冗余计算。
时间复杂度:O(kE),E为边数k平均值为2。
SPFA在形式上和BFS非常类似,不同的是BFS中一个点出了队列就不再重新进入队列,但是SPFA中一个点可能在出队列之后再次被放入队列,也就是说一个点修改过后其他点之后,过了一段时间可能会重新获得更短的路径,于是再次用来修改其他的点。
基本算法和Bellman-Ford一样,并且用如下的方法改进: 1、不是枚举所有节点,而是通过队列来进行优化,设立一个队列用来保存待优化的结点,优化时每次取出队首结点u,并且用u点当前的最短路径估计值对离开u点所指向的结点v进行松弛操作,如果v点的最短路径估计值有所调整,且v点不在当前的队列中,就将v点放入队尾。这样不断从队列中取出结点来进行松弛操作,直至队列空为止。 2、同时除了通过判断队列是否为空来结束循环,还可以通过下面的方法: 判断有无负环:如果某个点进入队列的次数超过V次则存在负环(SPFA无法处理带负环的图)
在这里我给大家展示一下伪代码,让大家对SPFA基本格式有所了解
伪代码:
初始化:dis[s]=0;pre[s]=0;dis[v]=MAXINT;
起点入队:team.push(s);inq[s]=1;
while(队列不空){
取出队首元素u;inq[u]=0;
for(与u相连的所有点v){
if(dis[v]>dis[u]+w){
dis[v]=dis[u]+w;
pre[v]=u;
if(!inq[v]){//若v不在队中,入队
v入队,inq[v]=1;
times[v]++;//入队次数加一
若改点入对次数大于总结点数,存在负权环,return;
}
}
}
5、Dijkstra算法+堆优化
普通的Dijkstra算法每次扩展一个距离最小的点,再更新与其相邻的点的距离。
如何寻找距离最小的点,普通的Dijkstra算法的思路是直接枚举,优化方案是建一个小根堆,小根堆里存储由当前结点更新距离的所有点,那么堆顶就是距离最小的点。理论复杂度(n+m)logn
对于不含负权边的图求单源最短路径,Dijkstra算法是高效的。但是在含负权边的图中,Dijkstra很可能得不到正确的结果,因为Dijkstra每次选的是当前能连到的边中权值最小的,在正权图中这种贪心是对的,但是在负权图中就不是这样了。
浙公网安备 33010602011771号