【最短路径】Dijkstra算法
Dijkstra算法
给定一个源顶点
算法分析
我们以下图为例,计算起点
我们先定义一个
初始条件
由于,我们从顶点
第一次查找
我们用
第二次查找
我们考虑,从
第三次查找
第四次查找
第五次查找
第六次查找
代码模板
这里我们直接给出代码模板:
import heapq from typing import List, Tuple class NodeState(object): def __init__(self, node_id: int, distance: int = 0): # 节点id self._id = node_id # 从起点到当前节点的距离 self._distance = distance def get_id(self): return self._id def get_distance(self): return self._distance def __lt__(self, other): """ 小的节点在堆顶 """ return self.get_distance() - other.get_distance() def dijkstra(start: int, graph: List[List[Tuple[int]]]): """ 单源最短路径:计算从起点到每个节点的距离 :param start: 起点 :param graph: 邻接表 :return: """ distances = [float("INF")] * len(graph) distances[start] = 0 # 使用优先级队列保存所有的节点,保证堆顶的元素最小 queue = list() heapq.heappush(queue, NodeState(start, 0)) while len(queue) != 0: current_stat = heapq.heappop(queue) if current_stat.get_distance() > distances[current_stat.get_id()]: continue for neighbor in graph[current_stat.get_id()]: # 当前节点的序号 _id = neighbor[0] # 起点到当前节点的距离 distance = distances[current_stat.get_id()] + neighbor[1] if distance < distances[_id]: distances[_id] = distance heapq.heappush(queue, NodeState(_id, distance)) return distances
应用
应用 1:Leetcode.743
题目
有 n 个网络节点,标记为 1 到 n。
给你一个列表 times,表示信号经过 有向 边的传递时间。,其中 是源节点, 是目标节点, 是一个信号从源节点传递到目标节点的时间。
现在,从某个节点 K 发出一个信号。需要多久才能使所有节点都收到信号?如果不能使所有节点收到信号,返回 -1 。
示例 1:
输入:times = [[2,1,1],[2,3,1],[3,4,1]], n = 4, k = 2
输出:2
题目分析
这是一道典型的求最短路径的算法:加权有向图,没有负权重边,求最短路径。
我们将传播时间看成边
这里我们直接给出
需要注意:题目中的节点序号是从
代码实现
【JAVA实现】:
class Solution { public int networkDelayTime(int[][] times, int n, int k) { List<List<Node>> graph = new ArrayList<>(); for (int i = 0; i < n; i++) { graph.add(new ArrayList<>()); } for (int[] time : times) { graph.get(time[0] - 1).add(new Node(time[1] - 1, time[2])); } int[] distances = dijkstra(k - 1, graph); int result = Integer.MIN_VALUE; for (int distance : distances) { result = Math.max(result, distance); } return result == Integer.MAX_VALUE ? -1 : result; } private int[] dijkstra(int start, List<List<Node>> graph) { int[] distances = new int[graph.size()]; Arrays.fill(distances, Integer.MAX_VALUE); distances[start] = 0; PriorityQueue<Node> queue = new PriorityQueue<>((a, b) -> b.getDistance() - a.getDistance()); queue.add(new Node(start, 0)); while (!queue.isEmpty()) { Node currentNode = queue.poll(); int u = currentNode.getId(); if (currentNode.getDistance() > distances[u]) { continue; } for (Node neighbor : graph.get(u)) { int v = neighbor.getId(); int distance = distances[u] + neighbor.getDistance(); if (distance < distances[v]) { distances[v] = distance; queue.offer(new Node(v, distance)); } } } return distances; } private static class Node { private final int id; private final int distance; public Node(int id, int distance) { this.id = id; this.distance = distance; } public int getId() { return id; } public int getDistance() { return distance; } } }
【Python实现】:
import heapq from typing import List class Solution: def networkDelayTime(self, times: List[List[int]], n: int, k: int) -> int: graph = [list() for _ in range(n)] for _time in times: graph[_time[0] - 1].append((_time[2], _time[1] - 1)) distances = self.dijkstra(graph, k - 1) result = max(distances) if max(distances) < float("INF") else -1 return int(result) def dijkstra(self, graph, start): distances = [float("INF")] * len(graph) distances[start] = 0 queue = list() heapq.heappush(queue, (0, start)) while queue: _node = heapq.heappop(queue) if distances[_node[1]] < _node[0]: continue for neighbor in graph[_node[1]]: distance = _node[0] + neighbor[0] if distance < distances[neighbor[1]]: distances[neighbor[1]] = distance heapq.heappush(queue, (distance, neighbor[1])) return distances
应用 2:Leetcode.1514
题目
给你一个由 n 个节点(下标从 0 开始)组成的无向加权图,该图由一个描述边的列表组成,其中 edges[i] = [a, b] 表示连接节点 a 和 b 的一条无向边,且该边遍历成功的概率为 succProb[i] 。
指定两个节点分别作为起点 start 和终点 end ,请你找出从起点到终点成功概率最大的路径,并返回其成功概率。如果不存在从 start 到 end 的路径,请 返回 0 。只要答案与标准答案的误差不超过 1e-5 ,就会被视作正确答案。
示例 1:
输入:n = 3, edges = [[0,1],[1,2],[0,2]], succProb = [0.5,0.5,0.2], start = 0, end = 2
输出:0.25000
解释:从起点到终点有两条路径,其中一条的成功概率为 0.2 ,而另一条为 0.5 * 0.5 = 0.25
解题思路
这道题的特点:边有非负的权重,求单源最优路径。
可以转换为
题目中的图是无向图,我们可以将无向图的边可以看成是双向的边即可。
首先,我们首先将图中的边转换为邻接表
我们用一个优先级队列
然后,从起点开始更新邻近节点的概率,最后返回终点的概率即可。
代码实现
class Solution { public double maxProbability(int n, int[][] edges, double[] successProb, int start_node, int end_node) { List<List<Node>> graph = new ArrayList<>(); for (int i = 0; i < n; i++) { graph.add(new ArrayList<>()); } // 使用邻接矩阵构造无向图 for (int i = 0; i < successProb.length; i++) { graph.get(edges[i][0]).add(new Node(edges[i][1], successProb[i])); graph.get(edges[i][1]).add(new Node(edges[i][0], successProb[i])); } double[] distances = dijkstra(start_node, graph, n); return distances[end_node] == Integer.MAX_VALUE ? 0 : distances[end_node]; } private double[] dijkstra(int start, List<List<Node>> graph, int n) { // 初始每个节点的概率为零 double[] distances = new double[n]; // 大顶堆 PriorityQueue<Node> q = new PriorityQueue<>((a, b) -> Double.compare(b.getDistance(), a.getDistance())); // 起点的概率设置为 1 distances[start] = 1; q.offer(new Node(start, 1)); while (!q.isEmpty()) { Node candidate = q.poll(); int u = candidate.getId(); // 如果堆顶的元素的权重更小,就跳过 if (candidate.getDistance() < distances[u]) { continue; } // 遍历邻近节点 for (Node neighbor : graph.get(u)) { int v = neighbor.getId(); // 起点到当前节点的权重 double distance = distances[u] * neighbor.getDistance(); if (distances[v] < distance) { distances[v] = distance; q.offer(new Node(v, distance)); } } } return distances; } private static class Node { private final int id; private final double distance; public Node(int id, double distance) { this.id = id; this.distance = distance; } public int getId() { return id; } public double getDistance() { return distance; } } }
注意:
- Python:这道题直接使用模板,会有用例超时,这里将顶点及其概率,直接通过元祖保存到优先级队列里面,避免创建对象引用的开销,避免用例超时。
- Java:使用邻接矩阵会超时,需要使用邻接表才能通过。
应用 3:Leetcode.1631
题目
你准备参加一场远足活动。给你一个二维 rows x columns 的地图 heights ,其中 heights[row][col] 表示格子 (row, col) 的高度。一开始你在最左上角的格子 (0, 0) ,且你希望去最右下角的格子 (rows-1, columns-1) (注意下标从 0 开始编号)。你每次可以往 上,下,左,右 四个方向之一移动,你想要找到耗费 体力 最小的一条路径。
一条路径耗费的 体力值 是路径上相邻格子之间 高度差绝对值 的 最大值 决定的。
请你返回从左上角走到右下角的最小 体力消耗值 。
示例 1:
输入:heights = [[1,2,2],[3,8,2],[5,3,5]]
输出:2
解释:路径 [1,3,5,3,5] 连续格子的差值绝对值最大为 2 。
这条路径比路径 [1,2,2,2,5] 更优,因为另一条路径差值最大值为 3 。
解题思路
这里的矩阵,我们可以类似的看成一个有向无环图,因为矩阵中的每一个点,都可以向四周邻近的点延伸。同时,需要注意,矩阵中已经搜索过的节点,不能再次重复搜索,每次搜索的时候,我们更新到达每个点所需要的权重(即题目中的体力值)。
显然,这是一个在有向加权图中,并且权重都是非负值,求源顶点
通常,我们使用
这里,我们可以直接使用给定的矩阵
更新完所有节点的权重之后,就可以得到源顶点
代码实现
class Solution { private static int[][] DIRECTIONS = new int[][]{{0, 1}, {1, 0}, {0, -1}, {-1, 0}}; public int minimumEffortPath(int[][] heights) { int m = heights.length, n = heights[0].length; int[][] distances = dijkstra(heights); return distances[m - 1][n - 1]; } private int[][] dijkstra(int[][] heights) { int m = heights.length, n = heights[0].length; // 记录起点到每一个位置的距离 int[][] distances = new int[m][n]; for (int i = 0; i < m; i++) { Arrays.fill(distances[i], Integer.MAX_VALUE); } // 维护一个小根堆 PriorityQueue<int[]> q = new PriorityQueue<>((a, b) -> a[2] - b[2]); // 初始化起点 distances[0][0] = 0; q.offer(new int[]{0, 0, 0}); // 维护一个visit数组,避免走回头路 boolean[][] visit = new boolean[m][n]; while (!q.isEmpty()) { int[] candidate = q.poll(); int u1 = candidate[0], u2 = candidate[1]; // 已经访问过,就跳过 if (visit[u1][u2]) { continue; } // 到达终点,则退出 if (u1 == m - 1 && u2 == n - 1) { break; } visit[u1][u2] = true; // 遍历邻近的节点 for (int[] direction : DIRECTIONS) { int v1 = u1 + direction[0]; int v2 = u2 + direction[1]; if (v1 < 0 || v1 >= m || v2 < 0 || v2 >= n) { continue; } // 如果到达位置(v1,v2) 需要消耗的能量更小,就更新到达这个位置的路径,并将其入队 int distance = Math.max(candidate[2], Math.abs(heights[v1][v2] - heights[u1][u2])); if (distance < distances[v1][v2]) { distances[v1][v2] = Math.max(candidate[2], distance); q.offer(new int[]{v1, v2, distance}); } } } return distances; } }
总结
本文作者:LARRY1024
本文链接:https://www.cnblogs.com/larry1024/p/16934206.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 【.NET】调用本地 Deepseek 模型
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· DeepSeek “源神”启动!「GitHub 热点速览」
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
· 我与微信审核的“相爱相杀”看个人小程序副业