[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組🙃