Dijkstra算法

Dijkstra算法

Dijkstra算法完成的是找到某個(gè)節(jié)點(diǎn)到其他各個(gè)節(jié)點(diǎn)的最短距離返回一個(gè)距離表,規(guī)定所有路線權(quán)重都是大于0的,一開(kāi)始需要給一個(gè)點(diǎn)椿息,因?yàn)橥瓿傻木褪钦疫@個(gè)點(diǎn)到其他各個(gè)節(jié)點(diǎn)的最短距離。

算法思路:

1)從已經(jīng)解鎖的點(diǎn)中找一個(gè)開(kāi)始點(diǎn)到它最近距離的點(diǎn)

2)解鎖這個(gè)點(diǎn)的所有邊坷衍,更新距離表寝优,距離表中一開(kāi)始沒(méi)有到達(dá)這個(gè)節(jié)點(diǎn)的就新增,之前有的就進(jìn)行更新枫耳。

3)每個(gè)點(diǎn)只處理一次乏矾,進(jìn)行了一次解鎖邊集和更新距離表后就將它鎖住,返回1繼續(xù)操作直到所有可達(dá)節(jié)點(diǎn)都處理一遍即可拿到最小距離表。

為什么Dijkstra算法能夠拿到最短距離呢钻心?

我簡(jiǎn)單說(shuō)說(shuō)凄硼,嚴(yán)格證明就不談,我也不會(huì)捷沸。首先摊沉,從已經(jīng)解鎖的點(diǎn)中找到可以到達(dá)的最小距離。比如下面的這個(gè)圖痒给,從v1開(kāi)始進(jìn)行算法说墨。

1.解鎖的有v1,拿出v1,v1所能到達(dá)的點(diǎn),{v6,v5,v3}解鎖更新距離表為{v1:0,v6:100,v5:30,v3:10}

2.從已經(jīng)解鎖的點(diǎn)集中找到可達(dá)的最短距離的點(diǎn)苍柏,那就是v3尼斧,為什么要拿最小的呢?為什么一個(gè)點(diǎn)處理完之后就可以不管了呢试吁?

原因是如果你是從已經(jīng)解鎖的點(diǎn)中找到的最短距離的點(diǎn)棺棵,那就說(shuō)明這就是開(kāi)始點(diǎn)能到達(dá)它的最短距離,就像是上面第二次處理v3潘悼,v3是v1的所有直達(dá)點(diǎn)中最短的律秃,那么就說(shuō)明從其他直達(dá)點(diǎn)走再繞回v3,在邊權(quán)值大于0的前提下距離不可能比v1直達(dá)v3要短治唤。

每次都選出一個(gè)可達(dá)的最短距離棒动,最終將所有點(diǎn)都找出來(lái),那么每個(gè)點(diǎn)肯定是可達(dá)最短距離的加和宾添,這就是貪心策略船惨。

image-20210226210336847.png

java實(shí)現(xiàn)Dijkstra算法1.0

public static HashMap<Node, Integer> dijkstra(Node begin) {
    if (begin == null) return null;
    HashMap<Node, Integer> nodeDistanceMap = new HashMap<>();
    nodeDistanceMap.put(begin, 0);
    //鎖住已經(jīng)處理過(guò)的節(jié)點(diǎn)
    HashSet<Node> selectedNode = new HashSet<>();
    //每次找到當(dāng)前距離最短的節(jié)點(diǎn)
    while (true) {
        int minDistance = Integer.MAX_VALUE;
        Node minNode = null;
        for (Map.Entry<Node, Integer> entry : nodeDistanceMap.entrySet()) {
            Integer distance = entry.getValue();
            if (!selectedNode.contains(entry.getKey()) && distance < minDistance) {
                minDistance = distance;
                minNode = entry.getKey();
            }
        }
        if (minDistance == Integer.MAX_VALUE) break;
        for (Edge next : minNode.edges) {
            if (!nodeDistanceMap.containsKey(next.to)) {
                nodeDistanceMap.put(next.to, minDistance + next.weight);
            } else {
                nodeDistanceMap.put(next.to, Math.min(nodeDistanceMap.get(next.to), minDistance + next.weight));
            }
        }
        //鎖住Node,以后不用管它了
        selectedNode.add(minNode);
    }
    return nodeDistanceMap;

改良一下代碼,不是優(yōu)化缕陕,就是將找最小到達(dá)點(diǎn)功能給抽離出來(lái)粱锐。

//將找到最小距離的節(jié)點(diǎn)給抽離出一個(gè)函數(shù),其實(shí)是再寫一遍扛邑,不太熟練
public static HashMap<Node, Integer> dijkstra1(Node begin) {
    if (begin == null) return null;
    HashMap<Node, Integer> distanceMap = new HashMap<>();
    distanceMap.put(begin, 0);
    HashSet<Node> lockedNodes = new HashSet<>();
    Node minNode;
    while ((minNode = getMinDistanceNode(distanceMap, lockedNodes)) != null) {
        for (Edge edge : minNode.edges) {
            Integer distance = distanceMap.get(minNode);
            Node to = edge.to;
            if (!distanceMap.containsKey(to)) {
                //add
                distanceMap.put(to, distance + edge.weight);
            } else {
                //update
                distanceMap.put(to, Math.min(distance + edge.weight, distanceMap.get(to)));
            }
        }
        lockedNodes.add(minNode);//鎖住
    }
    return distanceMap;
}

public static Node getMinDistanceNode(HashMap<Node, Integer> distanceMap, HashSet<Node> lockedNodes) {
    int min = Integer.MAX_VALUE;
    Node minNode = null;
    for (Map.Entry<Node, Integer> entry : distanceMap.entrySet()) {
        if (!lockedNodes.contains(entry.getKey()) && entry.getValue() < min) {
            min = entry.getValue();
            minNode = entry.getKey();
        }
    }
    return minNode;
}

優(yōu)化2.0版本怜浅,這才是進(jìn)行了優(yōu)化

思考:

通過(guò)研究dijkstra算法我們可以發(fā)現(xiàn),核心就兩點(diǎn)
1)每次都找到那個(gè)最小距離的到達(dá)點(diǎn)蔬崩,
2)遍歷那個(gè)最小距離的點(diǎn)更新距離表
很明顯第二點(diǎn)是不可能優(yōu)化的恶座,你總得看看所有的邊看看距離怎么樣然后更新距離表。
但是第一點(diǎn)找到最小距離沥阳,我們很容易會(huì)想到使用最小堆是否能夠優(yōu)化跨琳,答案是可以的。
因?yàn)樽钚《衙看芜M(jìn)行堆的調(diào)整也不過(guò)是logN,也就是說(shuō)我們可以直接使用一個(gè)小根堆來(lái)存儲(chǔ)距離表
不過(guò)注意一點(diǎn)桐罕,因?yàn)榫嚯x表中的距離是需要進(jìn)行更新的脉让,所以我們需要更新后保證堆正確性桂敛,這就不能使用PriorityQueue,需要我們自己實(shí)現(xiàn)resign功能,這需要維護(hù)一個(gè)indexMap需要知道哪個(gè)位置的元素發(fā)生了更新溅潜,這個(gè)更新只會(huì)變小术唬,不會(huì)變大,所以肯定是向上進(jìn)行heapInsert

如何在堆中完成一個(gè)節(jié)點(diǎn)的鎖住功能伟恶,避免已經(jīng)處理過(guò)的節(jié)點(diǎn)再次插入堆中?

做以下約定
1)如果indexMap中沒(méi)有這個(gè)Node碴开,那肯定還沒(méi)有,就直接加入
2)如果有博秫,但是index為-1潦牛,則表示加入過(guò),但是已經(jīng)彈出挡育,直接跳過(guò)巴碗,彈出的肯定是找到最小距離了

  1. 以上都不符合表示還在堆中,進(jìn)行更新操作即寒,需要維護(hù)堆

實(shí)現(xiàn)

用來(lái)找最小到達(dá)點(diǎn)的堆

public static class Heap {
    private static class NodeRecord {
        private Node node;
        private Integer distance;

        public NodeRecord(Node node, Integer distance) {
            this.node = node;
            this.distance = distance;
        }
    }

    private ArrayList<NodeRecord> eleMentData;//Entry<Node,distance>
    private HashMap<Node, Integer> indexMap;//這里的Integer是下標(biāo)
    private int heapSize;

    public Heap() {
        eleMentData = new ArrayList<>();
        indexMap = new HashMap<>();
    }

    public void swap(int aIdx, int bIdx) {
        //保證下標(biāo)隨著一起改
        NodeRecord aEntry = eleMentData.get(aIdx);
        NodeRecord bEntry = eleMentData.get(bIdx);
        eleMentData.set(aIdx, bEntry);
        eleMentData.set(bIdx, aEntry);
        indexMap.put(aEntry.node, bIdx);
        indexMap.put(bEntry.node, aIdx);
    }

    public void heapInsert(int index) {
        int father;
        //維護(hù)一個(gè)小根堆橡淆,如果父節(jié)點(diǎn)的distance比這個(gè)子節(jié)點(diǎn)要大的話需要交換
        while (compare(index, (father = (index - 1) / 2)) < 0) {
            swap(father, index);
        }
    }

    public void heapify(int index) {
        int leftChild;
        while ((leftChild = (index << 1) | 1) < heapSize) {
            int lowest = leftChild + 1 < heapSize && compare(leftChild + 1, leftChild) < 0 ? leftChild + 1 : leftChild;
            lowest = compare(index, lowest) < 0 ? index : lowest;
            if (index == lowest) break;
            swap(index, lowest);
            index = lowest;
        }
    }

    //比較兩個(gè)下標(biāo)處的Entry里distance大小
    private int compare(int i1, int i2) {
        return eleMentData.get(i1).distance - eleMentData.get(i2).distance;
    }

    //進(jìn)行三個(gè)情況的檢查和處理,完成操作
    public void addOrUpdateOrIgnore(Node node, int distance) {
        if (!indexMap.containsKey(node)) {//add
            eleMentData.add(heapSize, new NodeRecord(node, distance));
            //indexMap同步
            indexMap.put(node,heapSize++);
            heapInsert(heapSize - 1);
        } else if (indexMap.get(node) != -1) {//update
            Integer index = indexMap.get(node);
            eleMentData.get(index).distance = Math.min(eleMentData.get(index).distance, distance);
            heapInsert(index);//可能更新母赵,維護(hù)一下
        }
    }

    public NodeRecord poll() {
        if (isEmpty()) throw new RuntimeException("no more ele...");
        final NodeRecord res = eleMentData.get(0);
        swap(0,--heapSize);
        eleMentData.set(heapSize,null);
        indexMap.put(res.node,-1);//-1:曾經(jīng)來(lái)過(guò)
        heapify(0);
        return res;
    }

    public boolean isEmpty() {
        return heapSize == 0;
    }
}

算法本身

public static HashMap<Node,Integer> dijkstra(Node begin){
    if(begin==null) return null;
    HashMap<Node,Integer> result = new HashMap<>();
    Heap smallRHeap = new Heap();
    smallRHeap.addOrUpdateOrIgnore(begin,0);
    while (!smallRHeap.isEmpty()){
        Heap.NodeRecord heapTop = smallRHeap.poll();
        for(Edge edge:heapTop.node.edges){
            smallRHeap.addOrUpdateOrIgnore(edge.to,edge.weight+heapTop.distance);
        }
        result.put(heapTop.node,heapTop.distance);
    }

    return result;
}

測(cè)試

用上面那個(gè)例子進(jìn)行測(cè)試

public static void main(String[] args) {
    int[][] matrix = {{10, 1, 3}, {100, 1, 6}, {30, 1, 5}, {5, 2, 3}, {50, 3, 4}, {20, 5, 4}, {10, 4, 6}, {60, 5, 6},};
    Graph graph = GraphGenerator.graphGenerator(matrix);
    HashMap<Node, Integer> result = dijkstra(graph.nodesMap.get(1));
    for (Map.Entry<Node, Integer> entry : result.entrySet()) {
        System.out.println("to Node{" + entry.getKey().value + "} distance:" + entry.getValue());
    }
}

這里的Graph是我自己熟悉的圖的表示方式逸爵,matrix是一個(gè)邊組成的二維數(shù)組,里面存的每個(gè)一維數(shù)組都是這樣的意思{weight,from,to}

Graph

public class Graph{
    private HashMap<Integer,Node> nodes;//Node.value map to Node
    private HashSet<Edge> edges;
    public Graph(){
        nodes = new HashMap<>();
        edges = new HashSet<>();
    }
}

Node

public class Node{
    private int in;
    private int out;
    private int value;
    private ArrayList<Node> nexts;
    private ArrayList<Edge> edges;
    public Node(){
        nexts = new ArrayList<>();
        edges = new ArrayList<>();
    }
}

Edge

public class Edge{
    private int weight;
    private int from;//from Node's value
    private int to;//to Node's value
    public Edge(int weight,int from,int to){
        this.weight = weight;
        this.from = from;
        this.to = to;
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末凹嘲,一起剝皮案震驚了整個(gè)濱河市师倔,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌周蹭,老刑警劉巖趋艘,帶你破解...
    沈念sama閱讀 217,509評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異凶朗,居然都是意外死亡瓷胧,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門棚愤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)搓萧,“玉大人,你說(shuō)我怎么就攤上這事宛畦∶妫” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 163,875評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵刃永,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我羊精,道長(zhǎng)斯够,這世上最難降的妖魔是什么囚玫? 我笑而不...
    開(kāi)封第一講書人閱讀 58,441評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮读规,結(jié)果婚禮上抓督,老公的妹妹穿的比我還像新娘。我一直安慰自己束亏,他們只是感情好铃在,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著碍遍,像睡著了一般定铜。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上怕敬,一...
    開(kāi)封第一講書人閱讀 51,365評(píng)論 1 302
  • 那天揣炕,我揣著相機(jī)與錄音,去河邊找鬼东跪。 笑死畸陡,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的虽填。 我是一名探鬼主播丁恭,決...
    沈念sama閱讀 40,190評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼斋日!你這毒婦竟也來(lái)了牲览?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 39,062評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤桑驱,失蹤者是張志新(化名)和其女友劉穎竭恬,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體熬的,經(jīng)...
    沈念sama閱讀 45,500評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡痊硕,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評(píng)論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了押框。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片岔绸。...
    茶點(diǎn)故事閱讀 39,834評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖橡伞,靈堂內(nèi)的尸體忽然破棺而出盒揉,到底是詐尸還是另有隱情,我是刑警寧澤兑徘,帶...
    沈念sama閱讀 35,559評(píng)論 5 345
  • 正文 年R本政府宣布刚盈,位于F島的核電站,受9級(jí)特大地震影響挂脑,放射性物質(zhì)發(fā)生泄漏藕漱。R本人自食惡果不足惜欲侮,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望肋联。 院中可真熱鬧威蕉,春花似錦、人聲如沸橄仍。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,779評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)侮繁。三九已至虑粥,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間鼎天,已是汗流浹背舀奶。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,912評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留斋射,地道東北人育勺。 一個(gè)月前我還...
    沈念sama閱讀 47,958評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像罗岖,于是被迫代替她去往敵國(guó)和親涧至。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容