一.算法原理
1.基本原理
Dijkstra算法是利用广度优先搜索思想(BFS)的一种单源最短路径算法,相对于简单粗暴时间复杂度为O(n^3)的Floyed算法,(详情见我另一篇博客 只有五行的Floyed算法及全路径输出),Dijkstra算法的时间复杂度则有好的多,为O(n^2)。
该算法以起点为中心,将相邻边加入到优先队列中,每次弹出队列中距起点最近的点,利用伸缩操作(relaxation),更新该点各相邻节点到源点的最近距离(这里用到了贪心算法原理), 存入到一个集合disTo中,该集合中记录每一个点到源点的最近距离。
伪代码:
while(!queue.isEmpty())
//利用最小优先队列,取到源点距离最近节点
node = queue.poll()
for each neighbor node的相邻节点
if neighbor没有被访问过
queue.push(neightbor)
if disTo.get(neighbor) > disTo.get(node) + node到该相邻点距离
//记录路径
neighbor.parent = node;
//relaxation伸缩操作
disTo.put(neighbor, disTo.get(node) + node到该相邻点距离)
//其他操作
todo...
2.如何保存最短路径?
在pathTo集合中,设置此节点的上一节点。如果这点没有被访问过,就加入到优先队列中,就这样重复操作层层向外遍历,最后就可以生成一个最短路径树,对于从该源点到某一点的最短路径问题,只要看该点是否被访问过,被访问过的点说明存在最短路径,回溯pathTo集合,如pathTo(A) = B, B是使A到源点距离最近的相邻点(由贪心算法可知),pathTo(B) = C , C是使B到源点距离最近的相邻点,反复操作,直到pathTo(X) = 源点。即可得到最短路径
二.算法实现
package com.example.Dijkstra;
import java.util.*;
public class Dijkstra {
// <1, [2, 3]>表示节点1的父节点是节点2,到源点距离为3
private Map<Integer, int[]> disTo;
/**
* @param edges 一个节点到其他节点的距离
* [[0, 1, 1], [1, 2, 2]] 表示点0到点1的距离为1,点1到点2的距离为2
* @param n 所有节点个数 1<= n <= 1000
* @param k 源节点 1< k <n
*/
public Map<Integer, int[]> Dijkstra(int[][] edges, int n, int k) {
//通过edges数组生成有向图
//<0, <{1,2}, {2,3}>>表示节点0有1,2两个相邻节点,距离分别为2, 3
Map<Integer, List<int[]>> graph = new HashMap<>();
for (int[] edge : edges) {
if (!graph.containsKey(edge[0]))
graph.put(edge[0], new ArrayList<>());
graph.get(edge[0]).add(new int[]{edge[1], edge[2]});
}
//初始化disTo
disTo = new HashMap<>();
for(int i = 0; i<n; i++){
if(i==k)
disTo.put(i, new int[]{0, 0});
else disTo.put(i, new int[]{-1, Integer.MAX_VALUE});
}
//Dijkstra
//省略了最小优先队列,用isSeen数组辅助disTo来取代
boolean[] isSeen = new boolean[n];
while(true){
//得到距离最近点
int candiNode = -1;
int candiDistance = Integer.MAX_VALUE;
for(int i=0;i<n;i++){
int[] temp = disTo.get(i);
if(!isSeen[i] && temp[1]<candiDistance){
candiNode = i;
candiDistance = temp[1];
}
}
if(candiNode == -1) break;
isSeen[candiNode] = true;
if(graph.containsKey(candiNode))
for(int[] edge : graph.get(candiNode)){
if(disTo.get(edge[0])[1]> candiDistance + edge[1]){
//对该点相邻点进行伸缩操作
disTo.get(edge[0])[1] = candiDistance + edge[1];
//更新父节点
disTo.get(edge[0])[0] = candiNode;
}
}
}
return disTo;
}
/**
* 输出结果
* @param disTo
* @param end
*/
public void printPath(Map<Integer, int[]> disTo,int pathTo){
int distance = disTo.get(pathTo)[1];
List<Integer> path = new ArrayList<>();
int temp = pathTo;
path.add(temp);
while (temp!=0 && temp!=-1){
temp = disTo.get(temp)[0];
path.add(temp);
}
System.out.print("从初始节点到节点"+end+"的最短距离为"
+distance+"\n"+"最短路径为:\n"+path.get(0));
for(int i=1;i<path.size();i++){
System.out.print("<--"+path.get(i));
}
}
}
1.测试
输入:
edges: | n: | k: | pathTo |
---|---|---|---|
{{0, 1, 15},{0, 3, 5}, | 6 | 0 | 4 |
{1, 5, 6}, {3, 5, 20}, | |||
{1, 2, 15}, {3, 2, 30}, | |||
{2, 4, 10}, {5, 4, 9}}; |
预计输出:最短距离:30 路径:0-->1-->5-->4
环境:windows10,java11
2.结果
会了Dijkstra,不会DFS那更不得行,帮你安排了DFS的递归实现与堆栈实现