第二十章 Dijkstra算法和Floyd算法
一、Dijkstra算法
1.介绍
Dijkstra 算法是典型的单源最短路径算法,用于计算某个顶点到其他所有顶点的最短路径。Dijkstra 算法要求图中不存在负权边,即保证图中每条边的权重值为正。算法的基本思想是:从源点出发,每次选择离源点最近的一个顶点前进,然后以该顶点为中心进行扩展,最终得到源点到其余所有点的最短路径
第1步,创建距离表和前置顶点表。
距离表的 Key 是顶点名称,Value 是从起点 A 到对应顶点的已知最短距离,默认为无穷大;前置顶点表的 Key 是顶点名称,Value 是从起点A到对应顶点的已知最短路径的前置定点
第 2 步,遍历起点A,找到起点 A 的邻接顶点 B 和 C。从 A 到 B 的距离是 5,从 A 到 C 的距离是 2。把这一信息刷新到距离表当中
同时,顶点 B、C 的前置顶点都是 A,顶点 A 在邻接表中下标是 0,所以把前置顶点表的 B、C 值更新为 0
第 3 步,从距离表中找到从 A 出发距离最短的点,也就是顶点 C
第 4 步,遍历顶点 C,找到顶点 C 的邻接顶点 D 和 F(A 已经遍历过,不需要考虑)。从 C 到 D 的距离是 6,所以 A到 D 的距离是 2+6=8。从 C 到 F 的距离是 8,所以从 A 到 F 的距离是 2+8=10。把这一信息刷新到表中
同时,顶点 D、F 的前置顶点都是 C,顶点 C 在邻接表中下标是 2,所以把前置顶点表的 D、F 值更新为 2
接下来重复第 3 步、第 4 步所做的操作
第 5 步,也就是第 3 步的重复,从距离表中找到从A出发距离最短的点(C 已经遍历过,不需要考虑),也就是顶点 B
第 6 步,也就是第 4 步的重复,遍历顶点 B,找到顶点B的邻接顶点 D 和 E(A 已经遍历过,不需要考虑)。从 B 到 D 的距离是1,所以 A 到 D 的距离是 5+1=6,小于距离表中的 8;从 B 到 E 的距离是 6,所以从 A 到 E 的距离是 5+6=11。把这一信息刷新到表中
同时,顶点 D、E 的前置顶点都是 B,顶点 B 在邻接表中下标是 1,所以把前置顶点表的 D、E 值更新为 1
第 7 步,从距离表中找到从A出发距离最短的点(B 和 C 不用考虑),也就是顶点 D
第 8 步,遍历顶点 D,找到顶点 D 的邻接顶点 E 和 F。从 D 到 E 的距离是 1,所以 A 到 E 的距离是 6+1=7,小于距离表中的 11。从 D 到 F 的距离是 2,所以从 A 到 F 的距离是 6+2=8,小于距离表中的 10。把这一信息刷新到表中
同时,顶点 E、F 的前置顶点都是 D,顶点 D 在邻接表中下标是 3,所以把前置顶点表的 E、F 值更新为 3
第 9 步,从距离表中找到从A出发距离最短的点,也就是顶点 E
第 10 步,遍历顶点 E,找到顶点 E 的邻接顶点 G。从 E 到 G 的距离是 7,所以 A 到 G 的距离是 7+7=14。把这一信息刷新到表中
同时,顶点 G 的前置顶点是 E,顶点 E 在邻接表中下标是 4,所以把前置顶点表的 G 值更新为 4
第 11 步,从距离表中找到从A出发距离最短的点,也就是顶点 F。
第 12 步,遍历顶点 F,找到顶点 F 的邻接顶点 G。从 F 到 G 的距离是 3,所以 A 到 G 的距离是 8+3=11,小于距离表中的 14。把这一信息刷新到表中
就这样,除终点以外的全部顶点都已经遍历完毕,距离表中存储的是从起点 A 到所有顶点的最短距离,而前置定点存储的是从起点 A 到所有顶点最短路径的前置顶点
2.代码实现
package com.sisyphus.dijkstra;
import sun.awt.image.ImageWatched;
import java.util.LinkedList;
import java.util.List;
/**
* @Description: Dijkstra 算法$
* @Param: $
* @return: $
* @Author: Sisyphus
* @Date: 7/29$
*/
public class Dijkstra {
public static void main(String[] args) {
Graph graph = new Graph(7);
initGraph(graph);
int[] prevs = dijkstra(graph, 0);
printPrevs(graph.vertexes, prevs, graph.vertexes.length - 1);
}
//初始化图
private static void initGraph(Graph graph){
graph.vertexes[0] = new Vertex("A");
graph.vertexes[1] = new Vertex("B");
graph.vertexes[2] = new Vertex("C");
graph.vertexes[3] = new Vertex("D");
graph.vertexes[4] = new Vertex("E");
graph.vertexes[5] = new Vertex("F");
graph.vertexes[6] = new Vertex("G");
graph.adj[0].add(new Edge(1, 5));
graph.adj[0].add(new Edge(2, 2));
graph.adj[1].add(new Edge(0, 5));
graph.adj[1].add(new Edge(3, 1));
graph.adj[1].add(new Edge(4, 6));
graph.adj[2].add(new Edge(0, 2));
graph.adj[2].add(new Edge(3, 6));
graph.adj[2].add(new Edge(5, 8));
graph.adj[3].add(new Edge(1, 1));
graph.adj[3].add(new Edge(2, 6));
graph.adj[3].add(new Edge(4, 1));
graph.adj[3].add(new Edge(5, 2));
graph.adj[4].add(new Edge(1, 6));
graph.adj[4].add(new Edge(3, 1));
graph.adj[4].add(new Edge(6, 7));
graph.adj[5].add(new Edge(2, 8));
graph.adj[5].add(new Edge(3, 2));
graph.adj[5].add(new Edge(6, 3));
graph.adj[6].add(new Edge(4, 7));
graph.adj[6].add(new Edge(5, 3));
}
//图的顶点
private static class Vertex{
String data;
Vertex(String data){
this.data = data;
}
}
//图的边
private static class Edge{
int index;
int weight;
Edge(int index, int weight){
this.index = index;
this.weight = weight;
}
}
//图
private static class Graph{
private Vertex[] vertexes;
private LinkedList<Edge> adj[];
Graph(int size){
//初始化顶点和邻接矩阵
vertexes = new Vertex[size];
adj = new LinkedList[size];
for (int i = 0; i < adj.length; i++) {
adj[i] = new LinkedList<Edge>();
}
}
}
//打印最短路径
private static void printPrevs(Vertex[] vertexes, int[] prev, int i){
if(i>0){
printPrevs(vertexes, prev, prev[i]);
}
System.out.print(vertexes[i].data + " ");
}
//主算法
private static int[] dijkstra(Graph graph, int startIndex){
//图的顶点数量
int size = graph.vertexes.length;
//创建距离表,存储从起点到每一个顶点的已知最短路径的前置节点
int[] distances = new int[size];
//创建前置定点表,存储从起点到每一个顶点的已知最短路径的前置节点
int[] prevs = new int[size];
//记录顶点遍历状态
boolean[] access = new boolean[size];
//初始化最短路径表,到达每个顶点的路径的距离默认为无穷大
for (int i = 0; i < size; i++){
distances[i] = Integer.MAX_VALUE;
}
//遍历起点,刷新距离表
access[0] = true;
List<Edge> edgesFromStart = graph.adj[startIndex];
for (Edge edge : edgesFromStart){
distances[edge.index] = edge.weight;
prevs[edge.index] = 0;
}
//主循环,重复遍历最短距离顶点和刷新距离表的操作
for (int i = 1; i < size; i++) {
//寻找最短距离顶点
int minDistanceFromStart = Integer.MAX_VALUE;
int minDistanceIndex = -1;
for (int j = 1; j < size; j++){
if (!access[j] && distances[j] < minDistanceFromStart){
minDistanceFromStart = distances[j];
minDistanceIndex = j;
}
}
if (minDistanceIndex == -1){
break;
}
//遍历顶点,刷新距离表
access[minDistanceIndex] = true;
for (Edge edge : graph.adj[minDistanceIndex]){
if (access[edge.index]){
continue;
}
int weight = edge.weight;
int preDistance = distances[edge.index];
if (weight != Integer.MAX_VALUE && (minDistanceFromStart+ weight < preDistance)){
distances[edge.index] = minDistanceFromStart + weight;
prevs[edge.index] = minDistanceIndex;
}
}
}
return prevs;
}
}
时间复杂度为 O(n^2)
二、Floyd算法
1.介绍
Floyd 算法是一个经典的动态规划算法。其主要思想为:从任意顶点 u 到任意顶点v的最短路径不外乎 2 种可能,一是直接从 u 到v,二是从 u 经过若干个顶点 k 到 v。所以,我们假设 dist(u,v) 为顶点 u 到顶点 v 的最短路径的距离,对于每一个顶点 k,我们检查 dist(u,k) + dist(k,v) < dist(u,v) 是否成立,如果成立,证明从 u 到 k 再到 v 的路径比 u 直接到 v 的路径短,我们便设置 dist(u,v) = dist(u,k) + dist(k,v),这样一来,当我们遍历完所有顶点 k,dist(u,v) 中记录的便是 u 到 v 的最短路径的距离
第 1 步,构建带权图的邻接矩阵
在邻接矩阵当中,每一个数字代表着从某个顶点到另一个顶点的直接距离,这个距离是没有涉及到任何中继顶点的
第 2 步,只允许以顶点 A 作为中继顶点,B 和 C 之间的距离原本是无穷大,此时以 A 为中继,距离缩短为 AB距离 + AC距离 =
5 + 2 = 7
更新对应矩阵元素(橙色区域代表顶点A到其他顶点的临时距离)
第 3 步,以顶点 A、B 作为中继顶点
A 和 D 之间的距离原本是无穷大,此时以 B 为中继,距离缩短为 AB 距离 + BD距离 = 5 + 1 = 6
A 和 E 之间的距离原本是无穷大,此时以 B 为中继,距离缩短为 AB距离 + BE距离 = 5 + 6 = 11
更新对应矩阵元素(橙色区域代表顶点B到其他顶点的临时距离)
第 4 步,以顶点 A、B、C 作为中继顶点
A 和 F 之间的距离原本是无穷大,此时以 C 为中继,距离缩短为 AC距离 + CF距离 = 2 + 8 = 10
更新对应矩阵元素(橙色区域代表顶点C到其他顶点的临时距离)
…
…
以此类推,不断引入新的中继顶点,不断刷新矩阵中的临时距离
最终,当所有顶点都可以作为中继顶点时,我们的距离矩阵更新如下
此时,矩阵中每一个元素,都对应着某顶点到另一个顶点的最短距离
2.代码实现
package com.sisyphus.floyd;
/**
* @Description: Floyd 算法$
* @Param: $
* @return: $
* @Author: Sisyphus
* @Date: 7/29$
*/
public class Floyd {
final static int INF = Integer.MAX_VALUE;
public static void main(String[] args) {
int[][] matrix = {
{0, 5, 2, INF, INF, INF, INF},
{5, 0, INF, 1, 6, INF, INF},
{2, INF, 0, 6, INF, 8, INF},
{INF, 1, 6, 0, 1, 2, INF},
{INF, 6, INF, 1, 0, INF, 7},
{INF, INF, 8, 2, INF, 0, 3},
{INF, INF, INF, INF, 7, 3, 0}
};
floyd(matrix);
}
public static void floyd(int[][] matrix){
//循环更新矩阵的值
for (int i=0; i<matrix.length; i++){
for (int j=0; j<matrix.length; j++){
for (int k=0; k<matrix.length; k++){
if (matrix[j][i] == INF || matrix[i][k] == INF){
continue;
}
matrix[j][k] = Math.min(matrix[j][k],matrix[j][i] + matrix[i][k]);
}
}
}
//打印结果
System.out.println("最短路径矩阵:");
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < matrix.length; j++){
System.out.printf("%2d ",matrix[i][j]);
}
System.out.println();
}
}
}
时间复杂度为 O(n^3)