[TOC]java
本文将简单比较一下图论中最短路的两大最短路算法:Floyd(弗洛伊德)算法与Dijkstra(迪杰斯特拉)算法,并阐述一下两大算法背后的算法原理(动态规划与贪心),并记录一下因为对算法本质理解不透彻,我是怎么把本身坑了。算法
Floyd算法本质上是一种动态规划算法,又称“插点法”。能够形象的解释为“若是两点间的路径长度,大于这两点统统过第三点链接的路径长度,那么就修正这两点的最短路径”。缓存
Floyd算法的Java实现以下ide
public class GraphAlgorithm { public static final int INFINITY = Integer.MAX_VALUE >> 4; public static void floyd(HashMap<Integer, HashMap<Integer, Integer>> graph) { for (Integer k : graph.keySet()) { for (Integer i : graph.keySet()) { if (k.equals(i)) { continue; } int ik = graph.get(i).get(k); if (ik == INFINITY) { continue; } for (Integer j : graph.keySet()) { int ij = graph.get(i).get(j); int kj = graph.get(k).get(j); if (ik + kj < ij) { graph.get(i).put(j, ik + kj); } } } } } }
很是简明的三重循环,是一个动态规划算法。函数
就是一个三重循环权值修正,就完成了全部顶点之间的最短路计算,时间复杂度是O(n^3)
。学习
其实Floyd算法很好理解和实现,没什么好说的,本质就是动态规划优化
Dijkstra算法本质上是一种贪心算法this
迪杰斯特拉算法主要特色是以起始点为中心向外层层扩展,从一个顶点到其他各顶点的最短路径算法,直到扩展到终点为止。spa
很难受。Dijkstra算法是一种单源最短路算法,在算法的缓存优化中,我忽略了必须是最短路为真的条件必须是“其他n-1个节点均获得最短路径”code
下面是错误的堆优化并缓存的dijkstra代码,而后分析缘由
public class GraphAlgorithm { public static final int INFINITY = Integer.MAX_VALUE >> 4; // 堆中保存的数据节点 public class HeapNode implements Comparable { private int value; private int id; public HeapNode(int id, int value) { this.value = value; this.id = id; } public int getValue() { return value; } public int getId() { return id; } @Override public int compareTo(Object o) { return Integer.compare(value, ((HeapNode) o).getValue()); } } // 堆优化的迪杰斯特拉算法 public static void dijkstraWithHeap( HashMap<Integer, HashMap<Integer, Integer>> graph, int fromNodeId, int toNodeId) { PriorityQueue<HeapNode> sup = new PriorityQueue<>(); HashMap<Integer, Integer> dist = new HashMap<>(); Set<Integer> found = new HashSet<>(); for (Integer vertex : graph.keySet()) { dist.put(vertex, INFINITY); } dist.put(fromNodeId, 0); sup.add(new HeapNode(fromNodeId, 0)); while (!sup.isEmpty()) { HeapNode front = sup.poll(); int nowShortest = front.getId(); int minWeight = front.getValue(); // 此处更新缓存?好像能够?我不知道 graph.get(fromNodeId).put(nowShortest, minWeight); graph.get(nowShortest).put(fromNodeId, minWeight); if (nowShortest == toNodeId) { // 致命错误,此处不能结束函数 return; } found.add(nowShortest); for (Integer ver : graph.get(nowShortest).keySet()) { int value = graph.get(nowShortest).get(ver); if (!found.contains(ver) && minWeight + value < dist.get(ver)) { dist.put(ver, minWeight + value); sup.add(new HeapNode(ver, minWeight + value)); } } } graph.get(fromNodeId).put(toNodeId, INFINITY); graph.get(toNodeId).put(fromNodeId, INFINITY); } }
Dijkstra是一种贪心算法,全部的最短路都只是基于已知状况作出的判断,因此在堆不为空(朴素Dijkstra是没有遍历完其他n-1个节点)以前不能结束算法,不然获得的答案多是错误的。
此前没有发现这个问题是由于数据量不够大,只有1000余条指令,因此这样的Dijkstra算法没有出错。
当数据量增大到5000条,其中384条最短路查询指令,有13条出错。仔细排查后才发现是Dijkstra的问题。(然而这时候提交时间已经截至了,C组预约🙃)
而Dijkstra算法不止最短路矩阵使用了,最少换成、最少票价、最小不满意度矩阵均使用了Dijkstra算法,但这些指令没有出错。我认为缘由以下:图的邻接表在数据量大的状况下,是一个稠密图,Dijkstra算法提早结束会致使缓存结果并不是实际的最短路。
主要仍是没有理解贪心算法的本质,致使了错误的修改。
贪心算法是指,在对问题求解时,老是作出在当前看来是最好的选择。也就是说,不从总体最优上加以考虑,贪心算法所作出的是在某种意义上的局部最优解。
之后必定好好学习算法。不知道之后算法课会不会很难……
那么正确的缓存方式应该是这样:
public class GraphAlgorithm { public static final int INFINITY = Integer.MAX_VALUE >> 4; // 堆优化的迪杰斯特拉算法 public static void dijkstraWithHeap( HashMap<Integer, HashMap<Integer, Integer>> graph, int fromNodeId, int toNodeId) { PriorityQueue<HeapNode> sup = new PriorityQueue<>(); HashMap<Integer, Integer> dist = new HashMap<>(graph.size()); Set<Integer> found = new HashSet<>(); for (Integer vertex : graph.keySet()) { dist.put(vertex, INFINITY); } dist.put(fromNodeId, 0); sup.add(new HeapNode(fromNodeId, 0)); while (!sup.isEmpty()) { HeapNode front = sup.poll(); int nowShortest = front.getId(); int minWeight = front.getValue(); if (found.contains(nowShortest)) { continue; } found.add(nowShortest); for (Integer ver : graph.get(nowShortest).keySet()) { int value = graph.get(nowShortest).get(ver); if (!found.contains(ver) && minWeight + value < dist.get(ver)) { dist.put(ver, minWeight + value); sup.add(new HeapNode(ver, minWeight + value)); } } } // 最后缓存数据 for (Integer ver : dist.keySet()) { int minWeight = dist.get(ver); graph.get(fromNodeId).put(ver, minWeight); graph.get(ver).put(fromNodeId, minWeight); } } }
原本能够是开心的A组,开心的满分,结果……唉😔
关于贪心和动态规划,让我想起来了一类很经典的题型,最少的钱的张数:
如今有5元、4元、3元以及1元的纸币,问7元最少要多少张纸币?
若是按照简单的贪心策略,就是7 = 5 + 1 + 1,但这显然是错的,显然7 = 4 + 3才是最优解。
若是是动态规划就不存在这个问题。
原题我记不清楚了,只记得大概坑点就是这个。当时看了题解才知道坑点是这个。
(惋惜当时太菜了不懂啥事动态规划,如今也菜)
大概就这样。算法真有趣。
请大佬们多多补充,说的不对或者很差的纠正一下。
2019.5.16
我果真强测凉了🙃果真C组🙃