单源最短路径
给定一个带权有向图G=(V,E)
,其中每条边的权是一个实数。另外,还给定V
中的一个顶点,称为源。要计算从源到其他所有各顶点的最短路径长度。这里的长度就是指路上各边权之和。这个问题通常称为单源最短路径问题。
Dijkstra算法
迪杰斯特拉算法(Dijkstra),是从一个顶点到其余各顶点的最短路径算法,解决的是有权图中最短路径问题。迪杰斯特拉算法主要特点是从起始点开始,采用贪心算法的策略,每次遍历到始点距离最近且未访问过的顶点的邻接节点,直到扩展到终点为止。
Dijkstra算法只能解决正权图问题,而要解决负权图,则需要另外一种办法SPFA算法,本文主要讲Dijkstra算法。
基本思想
现在考虑从1节点到各个顶点的最短路径,带权有向图为下图。
1→1
之间的最短路径,权值为0
1→2
之间只有一条路径1→2
,权值为2
1→3
之间只有一条路径1→3
,权值为3
1→4
之间有三条路径1→2→4
、1→4
、1→3→4
,三条路线中最短的路径为1→2→4
,权值为3
由此,最终的单源最短路径为:
因此,可以将Dijkstra步骤总结为:
- 初始化
dist[start]=0
- 找出一个与
start
点距离dist
最小的未确定最短路径的点x
,标记它为已确定的点 - 遍历所有以
x
为起点的边得到(x,y,d)
,如果dist[y]>dist[x]+d
,则更新dist[y]=dist[x]+d
- 重复2,3步直到所有的点都被标记为确定最短路径的点
对Dijkstra的堆优化
上面方法的时间复杂度为
O
(
n
2
)
O(n^2)
O(n2),其中查找最小dist
的时间复杂度为
O
(
n
)
O(n)
O(n),可以使用小根堆的方法去优化它。
受限定义一个优先队列pq
,设dist
值小的优先级更高,这样可以通过q.poll
来通过
O
(
log
n
)
O(\log n)
O(logn)的时间复杂度找到dist
中最小的点。
代码实现
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.PriorityQueue;
public class Template {
public int[] dijkstra(List<int[]>[] graph, int start) {
int[] dist = new int[graph.length];
Arrays.fill(dist, Integer.MAX_VALUE);
dist[start] = 0;
PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> a[1] - b[1]);
pq.offer(new int[]{start, 0});
while (!pq.isEmpty()) {
int[] edge1 = pq.poll();
int v1 = edge1[0], c1 = edge1[1];
if (c1 > dist[v1]) continue;
for (int[] edge2 : graph[v1]) {
int v2 = edge2[0];
int c2 = c1 + edge2[1];
if (c2 < dist[v2]) {
dist[v2] = c2;
pq.offer(new int[]{v2, c2});
}
}
}
return dist;
}
public static void main(String[] args) {
Template template = new Template();
int n = 4;
int[][] edges = new int[][]{
{0, 1, 2},
{1, 3, 1},
{0, 3, 4},
{0, 2, 3},
{2, 3, 3}
};
int start = 0;
List<int[]>[] graph = new ArrayList[n];
Arrays.setAll(graph, e -> new ArrayList<>());
for (int[] edge : edges) {
int u = edge[0], v = edge[1], c = edge[2];
graph[u].add(new int[]{v, c});
}
int[] dist = template.dijkstra(graph, start);
for (int item : dist) System.out.printf("%d ", item);
}
}
题目链接
参考文献: