【面向對象】記一次錯誤的Dijkstra算法優化—動態規劃與貪心

[TOC]java

沒有學過算法,請各位大佬們輕拍

本文將簡單比較一下圖論中最短路的兩大最短路算法:Floyd(弗洛伊德)算法與Dijkstra(迪傑斯特拉)算法,並闡述一下兩大算法背後的算法原理(動態規劃與貪心),並記錄一下因爲對算法本質理解不透徹,我是怎麼把本身坑了。算法

Floyd(弗洛伊德)算法

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(迪傑斯特拉)算法

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

相關文章
相關標籤/搜索