目录
算法背景
地图中的导航功能就是基于迪杰斯特拉算法(Dijkstra)实现的,力扣周赛中经常出现基于这个算法的变种题
算法描述
算法目标:给出一个起始点,我们可以求出到达其他所有点的最短路径
第一步: 初始化:起始点到各个顶点的权重都是INF,且各个顶点都没有被访问过,每个顶点都没有父节点。
第二步: 循环执行下面的步骤,直到所有顶点都被访问:
1、把当前的节点u设置为被访问状态;(刚开始时,当前顶点就是起始点,后边都是获取代价最小的顶点作为下一步要走的顶点,即优先队列的第一个)
2、考察当前可行走的所有边及其对应的顶点tempv, 更新从起始点经过当前顶点到可到达的所有顶点的代价,然后放入优先队列(从小到大排序)
a, 如果被考察的边对应的顶点被访问过,则跳过,继续考察下一条边及其对应的顶点,
b, 如果从其起始到对应顶点tempv的权重比从当前点u到tempv的权重大,则更新权重和父节点,并加入到优先队列中
假设有一个加权图如下所示:
边的权重标在边上。假设起点是 A
。
初始化
- 权重:
A=0
,B=INF
,C=INF
,D=INF
,E=INF
- 父节点:
A=null
,B=null
,C=null
,D=null
,E=null
- 优先队列:
[(A, 0)]
第一步
- 选择权重最小的顶点
A
,标记为已访问,从优先队列中弹出A。 - 更新
A
的邻接顶点:- 对
B
:A->B
权重是 2,更新B
的权重为 2,父节点为A
,加入优先队列。 - 对
D
:A->D
权重是 1,更新D
的权重为 1,父节点为A
,加入优先队列。
- 对
- 权重:
A=0
,B=2
,C=INF
,D=1
,E=INF
- 父节点:
A=null
,B=A
,C=null
,D=A
,E=null
- 优先队列:
[(D, 1), (B, 2)]
第二步
- 选择权重最小的顶点
D
,标记为已访问,从优先队列中弹出D。 - 更新
D
的邻接顶点:- 对
A
:D->A
权重是 1,A
已被访问,跳过。 - 对
B
:D->B
权重是 1 + 1 = 2,不更新,因为B
的权重已是 2。 - 对
E
:D->E
权重是 1 + 3 = 4,更新E
的权重为 4,父节点为D
,加入优先队列。
- 对
- 权重:
A=0
,B=2
,C=INF
,D=1
,E=4
- 父节点:
A=null
,B=A
,C=null
,D=A
,E=D
- 优先队列:
[(B, 2), (E, 4)]
第三步
- 选择权重最小的顶点
B
,标记为已访问,从优先队列中弹出B。 - 更新
B
的邻接顶点:- 对
A
:B->A
权重是 2,A
已被访问,跳过。 - 对
D
:B->D
权重是 2 + 1 = 3,不更新,因为D
的权重已是 1。 - 对
C
:B->C
权重是 2 + 4 = 6,更新C
的权重为 6,父节点为B
,加入优先队列。 - 对
E
:B->E
权重是 2 + 5 = 7,不更新,因为E
的权重已是 4。
- 对
- 权重:
A=0
,B=2
,C=6
,D=1
,E=4
- 父节点:
A=null
,B=A
,C=B
,D=A
,E=D
- 优先队列:
[(E, 4), (C, 6)]
第四步
- 选择权重最小的顶点
E
,标记为已访问, 从优先队列中弹出E。 - 更新
E
的邻接顶点:- 对
D
:E->D
权重是 4 + 3 = 7,不更新,因为D
的权重已是 1。 - 对
B
:E->B
权重是 4 + 5 = 9,不更新,因为B
的权重已是 2。 - 对
C
:E->C
权重是 4 + 5 = 9,不更新,因为C
的权重已是 6。
- 对
- 权重:
A=0
,B=2
,C=6
,D=1
,E=4
- 父节点:
A=null
,B=A
,C=B
,D=A
,E=D
- 优先队列:
[(C, 6)]
第五步
- 选择权重最小的顶点
C
,标记为已访问,从优先队列中弹出C。 C
没有需要更新的邻接顶点。- 权重:
A=0
,B=2
,C=6
,D=1
,E=4
- 父节点:
A=null
,B=A
,C=B
,D=A
,E=D
- 优先队列:
[]
所有顶点都已被访问,算法结束。
最终,从起点 A
到各顶点的最短路径权重和路径如下:
- 到
B
:权重 2,路径A -> B
- 到
C
:权重 6,路径A -> B -> C
- 到
D
:权重 1,路径A -> D
- 到
E
:权重 4,路径A -> D -> E
通过这个例子,可以看到 Dijkstra 算法是如何一步步找到从起点到各个顶点的最短路径的。
算法模版
public class Dijkstra {
static class Node {
int x; // 节点编号
int lenth;// 用于记录起点到该节点的最短路径的临时变量
public Node(int x, int lenth) {
this.x = x;
this.lenth = lenth;
}
}
public static void main(String[] args) {
int[][] map = new int[6][6];// 记录权值,顺便记录链接情况,可以考虑附加邻接表
initmap(map);// 初始化
boolean visited[] = new boolean[6];// 判断是否已经确定
int len[] = new int[6];// 用于记录起点到对应节点的最终的最短路径长度
for (int i = 0; i < 6; i++) {
len[i] = Integer.MAX_VALUE;
}
PriorityQueue<Node> q1 = new PriorityQueue<>((a, b) -> {
return a.lenth - b.lenth;
});
len[0] = 0;// 从0这个点开始
q1.add(new Node(0, 0));
while (!q1.isEmpty()) {
Node t1 = q1.poll();
int index = t1.x;// 节点编号
int length = t1.lenth;// 节点当前点距离
visited[index] = true;// 抛出的点确定
for (int i = 0; i < map[index].length; i++) {
if (map[index][i] > 0 && !visited[i]) {
Node node = new Node(i, length + map[index][i]);
if (len[i] > node.lenth)// 需要更新节点的时候更新节点并加入队列
{
len[i] = node.lenth;
q1.add(node);
}
}
}
}
for (int i = 0; i < 6; i++) {
System.out.println(len[i]);
}
}
static Comparator<Node> com = new Comparator<Node>() {
public int compare(Node o1, Node o2) {
return o1.lenth - o2.lenth;
}
};
private static void initmap(int[][] map) {
map[0][1] = 2;
map[0][2] = 3;
map[0][3] = 6;
map[1][0] = 2;
map[1][4] = 4;
map[1][5] = 6;
map[2][0] = 3;
map[2][3] = 2;
map[3][0] = 6;
map[3][2] = 2;
map[3][4] = 1;
map[3][5] = 3;
map[4][1] = 4;
map[4][3] = 1;
map[5][1] = 6;
map[5][3] = 3;
}
}
力扣刷题
public class LeetCode1976到达目的地的方案数 {
public int countPaths(int n, int[][] roads) {
boolean visited[] = new boolean[n];// 判断是否已经确定
int len[] = new int[n]; // 记录从起点到各节点的最短路径长度
int[] cnt = new int[n]; // 记录从起点到各节点的最短路径数量
for (int i = 0; i < n; i++) {
len[i] = Integer.MAX_VALUE;
}
int[][] map = initmap(roads, n);
PriorityQueue<Node> q1 = new PriorityQueue<>((a, b) -> {
return a.lenth - b.lenth;
});
len[0] = 0;// 从0这个点开始
q1.add(new Node(0, 0));
cnt[0] = 1; // start -> start的最短路径只有一条
while (!q1.isEmpty()) {
Node t1 = q1.poll();
int index = t1.x;// 节点编号
int length = t1.lenth;// 节点当前点距离
visited[index] = true;// 抛出的点确定
for (int i = 0; i < map[index].length; i++) {
if (map[index][i] > 0 && !visited[i]) {
Node node = new Node(i, length + map[index][i]);
if (len[i] > node.lenth)// 需要更新节点的时候更新节点并加入队列
{
cnt[i] = cnt[index];
len[i] = node.lenth;
q1.add(node);
} else if (len[i] == node.lenth) {
// 如果当前最短路径长度相等,则累加路径数量
// 如果发现有另一个路径,它的长度和当前的最短路径长度相等,那么这条路径也是最短路径之一。
// 因此,我们需要将这条路径的数量加到当前节点的最短路径数量 cnt[i] 中
cnt[i] = (cnt[i] + cnt[index]) % 1000000007;
}
}
}
}
return cnt[n - 1];
}
private int[][] initmap(int[][] roads, int n) {
int[][] map = new int[n][n];
for (int i = 0; i < roads.length; i++) {
map[roads[i][0]][roads[i][1]] = roads[i][2];
map[roads[i][1]][roads[i][0]] = roads[i][2];
}
return map;
}
class Node {
int x; // 节点编号
int lenth;// 用于记录起点到该节点的最短路径的临时变量
public Node(int x, int lenth) {
this.x = x;
this.lenth = lenth;
}
}
public static void main(String[] args) {
int[][] roads = { { 1, 0, 10 } };
new LeetCode1976到达目的地的方案数().countPaths(2, roads);
}
}
public class LeetCode2642设计可以求最短路径的图类 {
class Graph {
int[][] map = null;
boolean visited[] = null;
int len[] = null;
public Graph(int n, int[][] edges) {
map = new int[n][n];
visited = new boolean[n];// 判断是否已经确定
len = new int[n]; // 记录从起点到各节点的最短路径长度
for (int i = 0; i < edges.length; i++) {
map[edges[i][0]][edges[i][1]] = edges[i][2];
}
for (int i = 0; i < n; i++) {
len[i] = Integer.MAX_VALUE;
}
}
public void addEdge(int[] edge) {
dijkstra(edge[0]);
map[edge[0]][edge[1]] = edge[2];
}
public int shortestPath(int node1, int node2) {
// 先检查 node1 到 node2 之间是否有直接的边
if (map[node1][node2] > 0) {
return map[node1][node2];
}
return len[node2];
}
public void dijkstra(int source) {
Arrays.fill(visited, false);
Arrays.fill(len, Integer.MAX_VALUE);
PriorityQueue<Node> q1 = new PriorityQueue<>((a, b) -> {
return a.lenth - b.lenth;
});
len[source] = -1;// 从node1这个点开始
q1.add(new Node(source, 0));
while (!q1.isEmpty()) {
Node t1 = q1.poll();
int index = t1.x;// 节点编号
int length = t1.lenth;// 节点当前点距离
visited[index] = true;// 抛出的点确定
for (int i = 0; i < map[index].length; i++) {
if (map[index][i] > 0 && !visited[i]) {
Node node = new Node(i, length + map[index][i]);
if (len[i] > node.lenth)// 需要更新节点的时候更新节点并加入队列
{
len[i] = node.lenth;
q1.add(node);
}
}
}
}
}
class Node {
int x; // 节点编号
int lenth;// 用于记录起点到该节点的最短路径的临时变量
public Node(int x, int lenth) {
this.x = x;
this.lenth = lenth;
}
}
}
}
public class LeetCode2662前往目标的最小代价{
public int minimumCost(int[] start, int[] target, int[][] specialRoads) {
// 创建一个优先级队列,用于存储每个位置和到达该位置的代价,按代价从小到大排序
PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> {
return a[2] - b[2];
});
// 初始化结果为从起点到终点的直线距离
int res = dist(start, target);
// 将起点加入优先级队列
pq.offer(new int[] { start[0], start[1], 0 });
// 创建一个 HashMap 用于存储已访问过的位置和到达该位置的最小代价
Map<String, Integer> vis = new HashMap<>();
vis.put(getKey(start), 0);
while (!pq.isEmpty()) {
// 每次从队列中取出代价最小的位置
int[] p = pq.poll();
// 遍历所有特殊路径
for (int[] sp : specialRoads) {
int[] p1 = new int[] { sp[0], sp[1] };
int[] p2 = new int[] { sp[2], sp[3] };
// 计算从当前位置到特殊路径起点的代价,再加上特殊路径的代价
int d = p[2] + dist(p, p1) + sp[4];
String key = getKey(p2);
// 获取之前到达特殊路径终点的最小代价,如果没有则为从起点到终点的直线距离
int x = vis.getOrDefault(key, dist(start, p2));
// 如果新的代价小于之前的最小代价,更新最小代价
if (d < x) {
vis.put(key, d);
pq.offer(new int[] { p2[0], p2[1], d });
// 更新结果,当前位置到特殊路径终点的代价加上从特殊路径终点到目标位置的代价
res = Math.min(res, d + dist(p2, target));
}
}
}
return res;
}
// 计算两点之间的代价
private int dist(int[] p1, int[] p2) {
return Math.abs(p1[0] - p2[0]) + Math.abs(p1[1] - p2[1]);
}
// 将位置转换为字符串,用于在 HashMap 中作为键
private String getKey(int[] p) {
return p[0] + "_" + p[1];
}
}