Dijkstra最短路径算法及其优化
直接给出Java版本的普通实现方式:
/** * 最普通的实现形式,时间复杂度是O(n2),空间复杂度是O(n) * @param weight * @param start * @return */ public static int[] getDijkstraRes(int[][] weight,int start){ boolean[] visited = new boolean[weight.length]; int[] res = new int[weight.length]; for (int i = 0;i < res.length;i++){ res[i] = weight[start][i]; } // 查找 n - 1 次(n 为节点个数),每次确定一个节点 for (int i = 1;i < weight.length;i++){ int min = Integer.MAX_VALUE; int closedNode = 0; // 找出一个未标记的离出发点最近的节点 for (int j = 0;j < weight.length;j++){ if (j != start && !visited[j] && res[j] < min){ min = res[j]; closedNode = j; } } // 标记该节点为已经访问过 visited[closedNode] = true; //根据加入的节点更新res数组 for(int j = 0;j < weight.length;j++){ if (j == closedNode || weight[closedNode][j] == Integer.MAX_VALUE){ //与新添加进来的节点也是不可达的,就跳过 continue; } if (res[closedNode] + weight[closedNode][j] < res[j]){ //更新操作 res[j] = res[closedNode] + weight[closedNode][j]; } } } return res; }
该方法是最普通的实现方式,时间复杂度在O(n^2),空间复杂度为O(n),那么这个算法是否有可疑优化的地方呢?
答案是有的,主要可以优化的点在于:
// 找出一个未标记的离出发点最近的节点 for (int j = 0;j < weight.length;j++){ if (j != start && !visited[j] && res[j] < min){ min = res[j]; closedNode = j; } }
在这个循环中,需要找到未访问过的且与已被访问的节点相连的最短的点,需要遍历所有的边,即O(n),这里我们使用小根堆,就可以实现插入O(logn),读取O(1)的时间复杂度,可以使得时间复杂度降低。
/** * 作为小根堆里面元素的载体 */ static class Edge implements Comparable<Edge>{ int val; int to; @Override public int compareTo(Edge o) { //当本val大于传入的o的val时,返回正值,即本Edge应该往后面排 return this.val - o .val; } Edge(int _val,int _to){ val = _val; to = _to; } } /** * 使用了优先队列来选出与已访问集合相连的最短的节点, * 这样方式的时间复杂度是O((n+m)logn),会比遍历求最短节点的方法时间复杂度低 * @param weight * @param start * @return */ public static int[] getDijkstraRes2(int[][] weight,int start){ int[] res = new int[weight.length]; boolean[] visited = new boolean[weight.length]; for (int i = 0;i < weight.length;i++){ if(i != start){ res[i] = Integer.MAX_VALUE; } } Queue<Edge> queue = new PriorityQueue<>(); Edge firstEdge = new Edge(0,start); queue.add(firstEdge); while (!queue.isEmpty()){ //把最小的那个拿出来,此时我就不用遍历 Edge pollEdge = queue.poll(); int node = pollEdge.to; int val = pollEdge.val; if(visited[node]){ //已经访问过了,就不要再访问了 continue; } res[node] = val; visited[node] = true; for (int j = 0;j < weight.length;j++){ if (!visited[j] && weight[node][j] != Integer.MAX_VALUE //必须加上这个,否则下面相加可能会溢出导致判断错误 && res[j] > res[node] + weight[node][j]){ res[j] = res[node] + weight[node][j]; queue.add(new Edge(res[j],j)); } } } return res; }
在上面的这个版本中,时间复杂度为O((n+m)logn),其中m是边的条数,n是点的个数。
用法举例:
public static void main(String[] args) { int MAX = Integer.MAX_VALUE; // 无法到达时距离设为 Integer.MAX_VALUE int[][] weight={ {0,1,12,MAX,MAX,MAX}, {MAX,0,9,3,MAX,MAX}, {MAX,MAX,0,MAX,5,MAX}, {MAX,MAX,4,0,13,15}, {MAX,MAX,MAX,MAX,0,4}, {MAX,MAX,MAX,MAX,MAX,0} }; int start = 0; // 选择出发点 System.out.println(Arrays.toString(getDijkstraRes(weight, start))); System.out.println(Arrays.toString(getDijkstraRes2(weight, start))); }
Floyd算法
参考【最短路径Floyd算法详解推导过程】看完这篇,你还能不懂Floyd算法?还不会? - 掘金 (juejin.cn)
Dijkstra算法无法处理有负权值的有向图情况,因为对于访问过的(visited)点,会认为已经找到了最短路径(因为默认1+正数>1,但是如果有负权值就有可能使这个情况不适用),不会再重新判断了,会直接跳过。
为什么Floyd算法可以处理负权值的情况?因为它通过动态规划逐步构建解决方案,并在没有负权重环的情况下正确地考虑了所有可能的中间节点。它会逐步考虑更多的中间节点,从没有中间节点到包括所有中间节点。在每一步,它会检查是否通过添加新的中间节点可以改进路径,并更新路径长度。因此,即使存在负权值,也可以正确地更新路径,前提是没有负权重的环。
Floyd无法处理负权值环,什么是负权值环?一个环上的所有权值加起来为负数,则是负权值环;为什么不能处理?因为负权值环可以一直循环下去使得路径之和越来越小,Floyd算法无法正确处理这种情况。
下面给出代码:
/** * floyd可以求每个点到其他各个点的最短距离(多源最短路径问题,即多个出发点的最短路径),不需要start * 但是时间复杂度是o(n3),空间复杂度是O(n2); * 如果是单源路径问题,那么Dijkstra算法是比较合适的。 * @param weight * @return */ public static int[][] getMinDisByFloyd(int[][] weight){ int MAX = Integer.MAX_VALUE; int len = weight.length; for(int k = 0;k < len;k++){ for (int i = 0;i < len;i++){ for (int j = 0;j < len;j++){ if (weight[i][k] != MAX && weight[k][j] != MAX && weight[i][j] > weight[i][k] + weight[k][j]){ weight[i][j] = weight[i][k] + weight[k][j]; } } } } return weight; }
其实算法非常简单,就是三层for循环。只要有中间节点时的路径长度短于目前的长度时,就进行更新操作。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY