图论算法之最短路径(Dijkstra
、Floyd
、Bellman-ford
和SPFA
)
1、图论最短路径概述
图论算法为了求解一个顶点到另一个顶点的最短路径,即如果从图中某一顶点(称为源点)到达另一顶点(称为终点)的路径可能不止一条,如何找到一条路径,使得沿此路径各边上的权值总和(即从源点到终点的距离)达到最小,这条路径称为最短路径(shortestpath
)。
最短路径有很多特殊的情况,包括有向图还是无向图,有没有负权边等。通常解决图论中最短路径问题的算法有Dijkstra
、Floyd
、Bellman-ford
和SPFA
四种算法,下面逐一介绍各个算法的思想、流程和Java
代码实现。
2、Dijkstra算法
迪杰斯特拉算法主要特点是以起始点为中心向外层扩展,直到扩展到终点为止。迪杰斯特拉算法采用的是贪心策略,将Graph
中的节点集分为最短路径计算完成的节点集S
和未计算完成的节点集T
,每次将从T
中挑选V0->Vt
最小的节点Vt
加入S
,并更新V0
经由Vt
到T
中剩余节点的更短距离,直到T
中的节点全部加入S
中,它贪心就贪心在每次都选择一个距离源点最近的节点加入最短路径节点集合。迪杰斯特拉算法只支持非负权图,它计算的是单源最短路径,即单个源点到剩余节点的最短路径,时间复杂度为O(n²)
,如果求解整个图各个节点之间的最短路径,那么使用迪杰斯特拉算法的时间复杂度就是O(n³)
。
如果有权重是负值,则无法保证已经计算好的最短路径的节点构成最短路径,负数会影响结果。
Dijkstra算法适用场景:
一般算法书中都说适合于有向无环图(DAG
)。但是并不代表无向图就不行,只要将源点和目标点的权重相互之间赋值成一个值即可。如果是有环图的话,对于每个节点都是做了一层遍历,也不会出现死循环和重复计算的问题,因此迪杰斯特拉算法是可以用在无向图和有环图中的,适合于求单源最短路径。如果想适用于多源最短路径,就要将每个节点都进行遍历,那么时间复杂度就是O(n³)
。
把顶点的集合V
分成两组:
S
:{已经求出最短路径的顶点}T=V-S
:{尚未确定最短路径的顶点}
保证两点:
①每一个顶点对应一个距离值
S中顶点:从V0
到此顶点的最短路径长度
T中顶点:从V0
到此顶点的只包括S
中顶点作中间顶点的最短路径长度
②S
中各个顶点的距离值<=T
中各个顶点的距离值。
将T中顶点按照最短路径递增的次序加入到S
中,即每次从T
中找出距离值最小的顶点加入到S
中,直到S=V
为止。
算法步骤:
- 初始时,令
S={V0}
,T
={其余顶点},T中顶点Vi对应的距离值,有:- 若存在
<V0,Vi>
,为<V0,Vi>
弧上的权值; - 若不存在
<V0,Vi>
,为无穷大.
- 若存在
- 从
T
中选取一个其距离值为最小的顶点W
,加入S
,则S={V0, W}
; - 对T中其余顶点的距离值进行修改:若加进
W
作中间顶点,从V0
到Vi
的距离值比不加W
的路径要短,则修改此距离值; - 重复上述步骤,直到S中包含所有顶点,即
S=V
为止。
代码实现:
public class DijstraAlgorithm {
//不能设置为Integer.MAX_VALUE,否则两个Integer.MAX_VALUE相加会溢出导致出现负权
public static int MaxValue = 100000;
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
System.out.println("请输入顶点数和边数:");
//顶点数
int vertex = input.nextInt();
//边数
int edge = input.nextInt();
int[][] matrix = new int[vertex][vertex];
//初始化邻接矩阵
for (int i = 0; i < vertex; i++) {
for (int j = 0; j < vertex; j++) {
matrix[i][j] = MaxValue;
}
}
for (int i = 0; i < edge; i++) {
System.out.println("请输入第" + (i + 1) + "条边与其权值:");
int source = input.nextInt();
int target = input.nextInt();
int weight = input.nextInt();
matrix[source][target] = weight;
}
//单源最短路径,源点
int source = input.nextInt();
//调用dijstra算法计算最短路径
dijstra(matrix, source);
}
public static void dijstra(int[][] matrix, int source) {
//最短路径长度
int[] shortest = new int[matrix.length];
//判断该点的最短路径是否求出
int[] visited = new int[matrix.length];
//存储输出路径
String[] path = new String[matrix.length];
//初始化输出路径
for (int i = 0; i < matrix.length; i++) {
path[i] = new String(source + "->" + i);
}
//初始化源节点
shortest[source] = 0;
visited[source] = 1;
for (int i = 1; i < matrix.length; i++) {
int min = Integer.MAX_VALUE;
int index = -1;
for (int j = 0; j < matrix.length; j++) {
//已经求出最短路径的节点不需要再加入计算并判断加入节点后是否存在更短路径
if (visited[j] == 0 && matrix[source][j] < min) {
min = matrix[source][j];
index = j;
}
}
//更新最短路径
shortest[index] = min;
visited[index] = 1;
//更新从index跳到其它节点的较短路径
for (int m = 0; m < matrix.length;</