最短路径问题是指在一个加权有向图或无向图中,寻找两个节点之间权值和最小的路径。其中,权值可以表示边的距离、耗费、时间等。
在无权图中,最短路径问题等价于广度优先搜索,可以使用BFS算法求解。而在带权图中,最短路径问题有多种算法可以求解,常见的包括Dijkstra算法、Bellman-Ford算法、Floyd算法等。
Dijkstra算法是一种贪心算法,用于求解单源最短路径问题。它维护一个距离数组,表示源节点到每个节点的最短距离,然后依次将距离最小的节点加入到已确定最短路径的集合中,更新与该节点相邻的节点的距离。该算法适用于图中没有负权边的情况。如果存在负权边,可以使用Bellman-Ford算法求解。
Floyd算法,又称为Floyd-Warshall算法或者插点法,是一种用于解决带权有向图中任意两点间最短路径的动态规划算法。Floyd算法采用的是迭代的思想,通过不断更新节点之间的路径长度来逐步求解最短路径。Floyd算法以其简短优雅著称,但在时间复杂度上不尽如人意,所以具体情况具体考虑。
今天就简单讲一下这两种基础的最短路径算法,欢迎斧正和提问。
Dijkstra算法
Dijkstra算法是一种经典的单源最短路径算法,用于求解一个带权重的有向图中,从一个源点到其他所有点的最短路径。该算法以源点为中心,一步一步地扩展到其他节点,直到找到所有节点的最短路径。
以下是Dijkstra算法的详细步骤:
-
初始化:将源点的距离设置为0,将其他节点的距离设置为正无穷。将源点加入已访问的节点集合,其他节点加入未访问的节点集合。
-
迭代更新:对于源点的所有邻居节点,更新其距离值。具体地,对于源点的每个邻居节点,计算出从源点到该节点的距离(即源点的距离加上邻居节点到源点的距离),如果该距离小于该节点当前的距离值,则更新该节点的距离值为该距离值。然后从未访问的节点集合中选取距离最小的节点作为下一个访问节点,将其加入已访问的节点集合。
-
重复执行第2步,直到所有节点都被访问,或者找到了目标节点。
-
最终输出:得到源点到各个节点的最短路径,以及最短路径的距离值。
Dijkstra算法的时间复杂度为O(V^2),其中V是节点数。如果使用优先队列来实现选取距离最小的节点,可以将时间复杂度降为O(E*logV),其中E是边数。因此,Dijkstra算法适用于节点数量较少的情况,对于大规模的图,需要使用其他算法来求解最短路径问题。
class Solution {
public int[] dijkstra(int[][] graph, int start) {
int n = graph.length;
int[] distances = new int[n]; // 存储起点到每个节点的最短距离
boolean[] visited = new boolean[n]; // 标记每个节点是否已被访问
int[] parent = new int[n];
// 初始化 distances 数组,起点到自身的距离为 0,其余节点的距离为无穷大
for (int i = 0; i < n; i++) {
distances[i] = Integer.MAX_VALUE;
}
distances[start] = 0;
parent[start] = start;
// 依次找到起点到各个节点的最短路径
for (int i = 0; i < n; i++) {
int u = -1;
int minDist = Integer.MAX_VALUE;
// 找到未访问节点中距离起点最近的节点
for (int j = 0; j < n; j++) {
if (!visited[j] && distances[j] < minDist) {
u = j;
minDist = distances[j];
}
}
if (u == -1) {
break; // 如果找不到距离起点最近的未访问节点,退出循环
}
visited[u] = true;
System.out.println(u + " " + parent[u] +" "+ distances[u]);
// 更新与节点 u 相邻的节点的距离
for (int v = 0; v < n; v++) {
if (graph[u][v] != Integer.MAX_VALUE && !visited[v] && distances[u] + graph[u][v] < distances[v]) {
distances[v] = distances[u] + graph[u][v];
parent[v] = u;
}
}
}
return distances;
}
}
Floyd算法
Floyd 算法是一种用于计算图中任意两点之间最短路径的算法,它是一种动态规划算法。
Floyd 算法的基本思想是:从图中任意一个节点出发,依次经过图中的每一个节点,最终到达目标节点,计算出该路径的长度,并比较该路径长度与之前计算过的路径长度,取较小值。在计算过程中,用一个二维数组来存储任意两点之间的最短路径长度,并不断更新这个数组。
Floyd 算法在计算任意两点之间的最短路径时,需要进行三重循环,时间复杂度为 O(N^3),其中 N 表示图中节点的个数。因此,Floyd 算法的时间复杂度较高,不适用于节点数较多的大型图。
下面是 Floyd 算法的实现示例,假设图用邻接矩阵来表示,disti 表示节点 i 到节点 j 的距离。代码中使用一个二维数组 path 来记录任意两点之间的最短路径上的中间节点。
public void floyd(int[][] dist, int[][] path) {
int n = dist.length;
for (int k = 0; k < n; k++) {
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (dist[i][k] != Integer.MAX_VALUE && dist[k][j] != Integer.MAX_VALUE && dist[i][k] + dist[k][j] < dist[i][j]) {
dist[i][j] = dist[i][k] + dist[k][j];
path[i][j] = k;
}
}
}
}
}
在上述代码中,dist 数组表示任意两点之间的最短路径长度,path 数组表示任意两点之间的最短路径上的中间节点。在算法执行结束后,如果要输出节点 i 到节点 j 的最短路径,可以使用以下代码:
public void printPath(int[][] path, int i, int j) {
if (path[i][j] == -1) {
System.out.print(j + " ");
} else {
printPath(path, i, path[i][j]);
printPath(path, path[i][j], j);
}
}