图的最短路径---迪杰斯特拉(Dijkstra)算法浅析

时间:2021-10-12 22:16:37

什么是最短路径

在网图和非网图中,最短路径的含义是不一样的。对于非网图没有边上的权值,所谓的最短路径,其实就是指两顶点之间经过的边数最少的路径。

对于网图,最短路径就是指两顶点之间经过的边上权值之和最少的路径,并且我们称路径上的第一个顶点为源点,最后一个顶点为终点

解决最短问题的算法

Dijkstra算法

Floyd算法

SPFA算法

Dijkstra算法描述

算法的特点:Dijkstra算法使用广度优先搜索解决赋权有向图或无向图的单源最短路径问题,最终得到一个最短路径树

算法的思路:Dijkstra算法采用贪心的策略,声明一个数组ShortPathTable保存源点到各顶点的带权长度,声明一个数组Patharc来保存最短路径前驱顶点。

初始化时,源点v0v_0v0​的路径权重被赋予0(ShortPathTable[v0v_0v0​] = 0),表示v0v_0v0​到v0v_0v0​的路径为0。对于顶点v0v_0v0​能够直接到达的边(v0v_0v0​,vwv_wvw​),将ShortPathTable[vwv_wvw​]设为该边的权重,对于v0v_0v0​不能直接到达的所有顶点,将路径权重设为无限大。

初始时,Patharc的值全为0。还需要一个中间数组fin来表示是否已经求得源点v0v_0v0​到vwv_wvw​的最短路径,初始化时该数组值全部置为0,且将fin[v0v_0v0​]=1,表示v0v_0v0​到v0v_0v0​不需要求路径。

算法执行时,从ShortPathTable数组中选择最小值,该值为源点v0v_0v0​到该边对应顶点vkv_kvk​的最小路径。修改fin[vkv_kvk​]=1,表示目前已经找到最近的顶点。同时修正当前最短路径及距离。完成一次循环。

算法示例

我们求v0v_0v0​到v8v_8v8​的最短路径

ps:这个图应该竖着看…

图的最短路径---迪杰斯特拉(Dijkstra)算法浅析

第一步

初始化ShortPathTable为[0, 1, 5, ∞, ∞, ∞, ∞, ∞, ∞];fin数组为[1, 0, 0, 0, 0, 0, 0, 0],Patharc初始化为[0, 0, 0, 0, 0, 0, 0, 0]。

第二步

我们先找离源点最近的顶点,从ShortPathTable数组中可以得到离源点最近的顶点为v1v_1v1​,且最近距离为1,所以此时v1v_1v1​对应的fin[1]设为1,此时fin数组为[1, 1, 0, 0, 0, 0, 0, 0]。

第三步

在已经找到的v0v_0v0​到v1v_1v1​的最短路径基础上,对v1v_1v1​与其他顶点的边进行计算,得到v0v_0v0​与它们的当前最短距离。根据例子,min=1,所以本来ShortPathTable[2]=5,表示源点直接到达v2v_2v2​的距离为5,但是现在通过v1v_1v1​,源点到达v2v_2v2​的距离为min+3=4 < 5。v3v_3v3​,v4v_4v4​也是一个道理。我们既然发现通过v1v_1v1​到达其他顶点的距离如果小于源点直接到达该顶点的距离,那么我们就要修正ShortPathTable数组,此时ShortPathTable为[0, 1, 4, 8, 6, ∞, ∞, ∞, ∞],这个过程有个专业术语叫松弛,即 v0v_0v0​顶点到v2v_2v2​顶点(还有v3v_3v3​,v4v_4v4​)的路程即 ShortPathTable[2],通过 < v1v_1v1​,v2v_2v2​> 这条边松弛成功。这是 Dijkstra 算法的主要思想:通过“边”来松弛源点到其余各个顶点的路程。

而Patharc数组就是为了记录源点到某一顶点的最短路径它们的前驱为哪个顶点。以现在为例,Patharc[2]=Patharc[3]=Patharc[4]=1,表示源点v0v_0v0​到v2v_2v2​,v3v_3v3​,v4v_4v4​点的最短路径它们的前驱均为v1v_1v1​。

第四步

使用同样的原理,重新开始循环。

算法实现

对于上述网图的邻接矩阵为:

//INFINITY表示无穷大,程序中可以以65535代替
{
{0, 1, 5, INFINITY, INFINITY, INFINITY, INFINITY, INFINITY, INFINITY },
{1, 0, 3, 7, 5, INFINITY, INFINITY, INFINITY, INFINITY },
{5, 3, 0, INFINITY, 1, 7, INFINITY, INFINITY, INFINITY },
{INFINITY, 7, INFINITY, 0, 2, INFINITY, 3, INFINITY, INFINITY },
{INFINITY, 5, 1, 2, 0, 3, 6, 9, INFINITY},
{INFINITY, INFINITY, 7, INFINITY, 3, 0, INFINITY, 5, INFINITY},
{INFINITY, INFINITY, INFINITY, 3, 6, INFINITY, 0, 2, 7},
{INFINITY, INFINITY,INFINITY, INFINITY, 9, 5, 2, 0, 4},
{INFINITY, INFINITY, INFINITY, INFINITY, INFINITY, INFINITY, 7, 4,0}
}

具体程序如下:

package 最短路径;

/**
* 迪杰斯特拉算法
* @author shanlei
*
*/
public class Dijkstra {
private final int INFINITY = 65535;
public int MAXVEX;
public int[] Patharc; //P[v]的值为前驱顶点下标
public int[] ShortPathTable; //D[v]表示v0到v的最短路径长度和 /**
* 在这里直接使用矩阵了,忽略了网图转化为矩阵的过程
*/
public int[][] maze = {
{0, 1, 5, INFINITY, INFINITY, INFINITY, INFINITY, INFINITY, INFINITY },
{1, 0, 3, 7, 5, INFINITY, INFINITY, INFINITY, INFINITY },
{5, 3, 0, INFINITY, 1, 7, INFINITY, INFINITY, INFINITY },
{INFINITY, 7, INFINITY, 0, 2, INFINITY, 3, INFINITY, INFINITY },
{INFINITY, 5, 1, 2, 0, 3, 6, 9, INFINITY},
{INFINITY, INFINITY, 7, INFINITY, 3, 0, INFINITY, 5, INFINITY},
{INFINITY, INFINITY, INFINITY, 3, 6, INFINITY, 0, 2, 7},
{INFINITY, INFINITY,INFINITY, INFINITY, 9, 5, 2, 0, 4},
{INFINITY, INFINITY, INFINITY, INFINITY, INFINITY, INFINITY, 7, 4,0}
}; //简单的初始化数据
public Dijkstra() {
this.MAXVEX = this.maze.length;
this.Patharc = new int[MAXVEX];
this.ShortPathTable = new int[MAXVEX];
} //Dijkstra
public void ShortestPath_Dijkstra(int v0) {
int v, w, k = 0, min;
int[] fin = new int[MAXVEX]; //表示已经求得顶点v0到vw的最短路径 //初始化
for (v = 0; v < MAXVEX; v++) {
fin[v] = 0;
Patharc[v] = 0;
ShortPathTable[v] = this.maze[v0][v];
}
ShortPathTable[v0] = 0;
fin[v0] = 1; /**
* 主循环
*/
for (v = 1; v < MAXVEX; v++) {
min = INFINITY; for (w = 0; w < MAXVEX; w++) {
if((fin[w] == 0) && (ShortPathTable[w] < min)) {
k = w;
min = ShortPathTable[w];
}
}
fin[k] = 1;
/**
* 修正当前最短路径及距离
*/
for (w = 0; w < MAXVEX; w++) {
//如果经过v顶点的路径比现在这条路径的长度短的话
if((fin[w] == 0) && ((min + maze[k][w]) < ShortPathTable[w])) {
//说明找到了更短的路径,修改D[w]和P[w]
ShortPathTable[w] = min+ maze[k][w];
Patharc[w] = k; }
} /**
* 算法执行过程
* 输出算法执行过程中的数据和最终最短路径
*/
System.out.println("min=" + min);
System.out.println("---- final array --- ");
for (int i = 0; i < fin.length; i++) {
System.out.print(fin[i] + " ");
}
System.out.println();
System.out.println("---- D array --- ");
for (int i = 0; i < ShortPathTable.length; i++) {
System.out.print(ShortPathTable[i] + " ");
}
System.out.println();
System.out.println("---- P array --- ");
for (int i = 0; i < Patharc.length; i++) {
System.out.print(Patharc[i] + " ");
}
System.out.println();
System.out.println();
System.out.println();
} for (int i = 0; i < ShortPathTable.length; i++) {
System.out.print("D[" + i + "]=" + ShortPathTable[i] + " ");
}
System.out.println();
int z = Patharc.length - 1;
while(true) {
if(z==0) {
System.out.print(z);
break;
}
System.out.print(z + "<-");
z = Patharc[z];
}
} public static void main(String[] args) {
Dijkstra d = new Dijkstra();
d.ShortestPath_Dijkstra(0);
} }

迪杰斯特拉解决了从某个源点到其余各顶点的最短路径问题,从嵌套循环可以得出该算法的时间复杂度为O(n2n^2n2),其中每次找到离源点最近的顶点的时间复杂度是O(N),我们可以使用“堆”来进行优化,使得这一部分的时间复杂度降低到O(logN)。

另外对于边数M少于N2N^2N2的稀疏图来说(我们把M远小于N2N^2N2的图称为稀疏图,而M相对较大的图称为稠密图),我们可以用邻接表来代替邻接矩阵,使得整个时间复杂度优化到O(MlogN)。请注意!在最坏的情况下M就是N2N^2N2,这样的话MlogN要比N2N^2N2还要大。但是大多数情况下并不会有那么多边,因此MlogN要比N2N^2N2小很多。

参考:https://www.cnblogs.com/wangyuliang/p/9216511.html