沒有學過算法,請各位大佬們輕拍
本文將簡單比較一下圖論中最短路的兩大最短路算法:Floyd(弗洛伊德)算法與Dijkstra(迪傑斯特拉)算法,並闡述一下兩大算法背后的算法原理(動態規划與貪心),並記錄一下由於對算法本質理解不透徹,我是怎么把自己坑了。
Floyd(弗洛伊德)算法
Floyd算法本質上是一種動態規划算法,又稱“插點法”。可以形象的解釋為“如果兩點間的路徑長度,大於這兩點通通過第三點連接的路徑長度,那么就修正這兩點的最短路徑”。
Floyd算法的Java實現如下
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(迪傑斯特拉)算法
Dijkstra算法本質上是一種貪心算法
迪傑斯特拉算法主要特點是以起始點為中心向外層層擴展,從一個頂點到其余各頂點的最短路徑算法,直到擴展到終點為止。
很難受。Dijkstra算法是一種單源最短路算法,在算法的緩存優化中,我忽略了必須是最短路為真的條件必須是“其余n-1個節點均得到最短路徑”
下面是錯誤的堆優化並緩存的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組🙃