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

什么是最短路径

在网图和非网图中,最短路径的含义是不一样的。对于非网图没有边上的权值,所谓的最短路径,其实就是指两顶点之间经过的边数最少的路径。
对于网图,最短路径就是指两顶点之间经过的边上权值之和最少的路径,并且我们称路径上的第一个顶点为源点,最后一个顶点为终点

解决最短问题的算法

Dijkstra算法
Floyd算法
SPFA算法

Dijkstra算法描述

算法的特点:Dijkstra算法使用广度优先搜索解决赋权有向图或无向图的单源最短路径问题,最终得到一个最短路径树
算法的思路:Dijkstra算法采用贪心的策略,声明一个数组ShortPathTable保存源点到各顶点的带权长度,声明一个数组Patharc来保存最短路径前驱顶点。
初始化时,源点 v 0 v_0 v0的路径权重被赋予0(ShortPathTable[ v 0 v_0 v0] = 0),表示 v 0 v_0 v0 v 0 v_0 v0的路径为0。对于顶点 v 0 v_0 v0能够直接到达的边( v 0 v_0 v0 v w v_w vw),将ShortPathTable[ v w v_w vw]设为该边的权重,对于 v 0 v_0 v0不能直接到达的所有顶点,将路径权重设为无限大。
初始时,Patharc的值全为0。还需要一个中间数组fin来表示是否已经求得源点 v 0 v_0 v0 v w v_w vw的最短路径,初始化时该数组值全部置为0,且将fin[ v 0 v_0 v0]=1,表示 v 0 v_0 v0 v 0 v_0 v0不需要求路径。
算法执行时,从ShortPathTable数组中选择最小值,该值为源点 v 0 v_0 v0到该边对应顶点 v k v_k vk的最小路径。修改fin[ v k v_k vk]=1,表示目前已经找到最近的顶点。同时修正当前最短路径及距离。完成一次循环。

算法示例

我们求 v 0 v_0 v0 v 8 v_8 v8的最短路径
ps:这个图应该竖着看…
图的最短路径---迪杰斯特拉(Dijkstra)算法浅析_第1张图片
第一步
初始化ShortPathTable为[0, 1, 5, ∞, ∞, ∞, ∞, ∞, ∞];fin数组为[1, 0, 0, 0, 0, 0, 0, 0],Patharc初始化为[0, 0, 0, 0, 0, 0, 0, 0]。
第二步
我们先找离源点最近的顶点,从ShortPathTable数组中可以得到离源点最近的顶点为 v 1 v_1 v1,且最近距离为1,所以此时 v 1 v_1 v1对应的fin[1]设为1,此时fin数组为[1, 1, 0, 0, 0, 0, 0, 0]。
第三步
在已经找到的 v 0 v_0 v0 v 1 v_1 v1的最短路径基础上,对 v 1 v_1 v1与其他顶点的边进行计算,得到 v 0 v_0 v0与它们的当前最短距离。根据例子,min=1,所以本来ShortPathTable[2]=5,表示源点直接到达 v 2 v_2 v2的距离为5,但是现在通过 v 1 v_1 v1,源点到达 v 2 v_2 v2的距离为min+3=4 < 5。 v 3 v_3 v3 v 4 v_4 v4也是一个道理。我们既然发现通过 v 1 v_1 v1到达其他顶点的距离如果小于源点直接到达该顶点的距离,那么我们就要修正ShortPathTable数组,此时ShortPathTable为[0, 1, 4, 8, 6, ∞, ∞, ∞, ∞],这个过程有个专业术语叫松弛,即 v 0 v_0 v0顶点到 v 2 v_2 v2顶点(还有 v 3 v_3 v3 v 4 v_4 v4)的路程即 ShortPathTable[2],通过 < v 1 v_1 v1 v 2 v_2 v2> 这条边松弛成功。这是 Dijkstra 算法的主要思想:通过“边”来松弛源点到其余各个顶点的路程。
而Patharc数组就是为了记录源点到某一顶点的最短路径它们的前驱为哪个顶点。以现在为例,Patharc[2]=Patharc[3]=Patharc[4]=1,表示源点 v 0 v_0 v0 v 2 v_2 v2 v 3 v_3 v3 v 4 v_4 v4点的最短路径它们的前驱均为 v 1 v_1 v1
第四步
使用同样的原理,重新开始循环。

算法实现

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

//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( n 2 n^2 n2),其中每次找到离源点最近的顶点的时间复杂度是O(N),我们可以使用“堆”来进行优化,使得这一部分的时间复杂度降低到O(logN)。

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

你可能感兴趣的:(数据结构,Java,算法,数据结构学习)