1. 程式人生 > >【圖論】深入理解Dijsktra演算法

【圖論】深入理解Dijsktra演算法

1. 介紹

Dijsktra演算法是大牛Dijsktra於1956年提出,用來解決有向圖單源最短路徑問題;但是不能解決負權的有向圖,若要解決負權圖則需要用到Bellman-Ford演算法。Dijsktra演算法思想:在DFS遍歷圖的過程中,每一次取出離源點的最近距離的點,將該點標記為已訪問,鬆弛與該點相鄰的結點。

有向圖記為\(G=(n, m)\),其中,\(n\)為頂點數,\(m\)為邊數;且\(e[a,b]\)表示從結點\(a\)到結點\(b\)的邊。\(d[i]\)記錄源點到結點i的距離,\(U\)為未訪問的結點集合,\(V\)為已訪問的結點集合。Dijsktra演算法具體步驟如下:

  • 從集合\(U\)
    中尋找離源點最近的結點\(u\),並將結點\(u\)標記為已訪問(從集合\(U\)中移到集合\(V\)中)

\[u = \mathop {\arg \min } \limits_{i \in U} d[i]\]

  • 鬆弛與結點\(u\)相鄰的未訪問結點,更新d陣列

\[\mathop {d[i]} \limits_{i \in U} = \min \lbrace d[i]\ , \ d[u] + e[u,i] \rbrace\]

  • 重複上述操作\(n\)次,即訪問了所有結點,集合\(U\)為空

Dijsktra演算法的Java實現

/**
 * Dijkstra's Algorithm for finding the shortest path
 *
 * @param adjMatrix adjacency matrix representation of the graph
 * @param source    the source vertex
 * @param dest      the destination vertex
 * @return the cost for the shortest path
 */
public static int dijkstra(int[][] adjMatrix, int source, int dest) {
  int numVertex = adjMatrix.length, minVertex = source;
  // `d` marks the cost for the shortest path, `visit` marks whether has been visited or not
  int[] d = new int[numVertex], visit = new int[numVertex];
  Arrays.fill(d, Integer.MAX_VALUE);
  d[source] = 0;
  for (int cnt = 1; cnt <= numVertex; cnt++) {
    int lowCost = Integer.MAX_VALUE;
    // find the min-vertex which is the nearest among the unvisited vertices
    for (int i = 0; i < numVertex; i++) {
      if (visit[i] == 0 && d[i] < lowCost) {
        lowCost = d[i];
        minVertex = i;
      }
    }
    visit[minVertex] = 1;
    if (minVertex == dest) return d[dest];
    // relax the minVertex's adjacency vertices
    for (int i = 0; i < numVertex; i++) {
      if (visit[i] == 0 && adjMatrix[minVertex][i] != Integer.MAX_VALUE) {
        d[i] = Math.min(d[i], d[minVertex] + adjMatrix[minVertex][i]);
      }
    }
  }
  return d[dest];
}

複雜度分析

  • 時間複雜度:重複操作(即最外層for迴圈)n次,找出minNode操作、鬆弛操作需遍歷所有結點,因此複雜度為\(O(n^2)\).
  • 空間複雜度:開闢兩個長度為n的陣列d與visit,因此複雜度為\(T(n)\).

2. 優化

從上述Java實現中,我們發現:(裡層for迴圈)尋找距離源點最近的未訪問結點\(u\),通過遍歷整個陣列來實現的,缺點是重複訪問已經訪問過的結點,浪費了時間。首先,我們來看看堆的性質。

堆是一種完全二叉樹(complete binary tree);若其高度為h,則1~h-1層都是滿的。如果從左至右從上至下從1開始給結點編號,堆滿足:

  • 結點\(i\)
    的父結點編號為\(i/2\).
  • 結點\(i\)的左右孩子結點編號分別為\(2*i\), \(2*i+1\).

如果結點\(i\)的關鍵值小於父結點的關鍵值,則需要進行上浮操作(move up);如果結點\(i\)的關鍵值大於父結點的,則需要下沉操作(move down)。為了保持堆的整體有序性,通常下沉操作從根結點開始。

小頂堆優化Dijsktra演算法

我們可以用小頂堆來代替d陣列,堆頂對應於結點\(u\);取出堆頂,然後刪除,如此堆中結點都是未訪問的。同時為了記錄陣列\(d[i]\)中索引\(i\)值,我們讓每個堆結點掛兩個值——頂點、源點到該頂點的距離。演算法虛擬碼如下:

Insert(vertex 0, 0)  // 插入源點
FOR i from 1 to n-1:  // 初始化堆
    Insert(vertex i, infinity)

FOR k from 1 to n:
    (i, d) := DeleteMin()
    FOR all edges ij:
        IF d + edge(i,j) < j’s key
            DecreaseKey(vertex j, d + edge(i,j))
  1. Insert(vertex i, d)指在堆中插入堆結點(i, d)。
  2. DeleteMin()指取出堆頂並刪除,時間複雜度為\(O(\log n)\)
  3. DecreaseKey(vertex j, d + edge(i,j))是鬆弛操作,更新結點(vertex j, d + edge(i,j)),需要進行上浮,時間複雜度為\(O(\log n)\)

我們需要n次DeleteMin,m次DecreaseKey,優化版的演算法時間複雜度為\(O((n+m)\log n)\).

程式碼實現

鄰接表
每一個鄰接表的表項包含兩個部分:頭結點、表結點,整個鄰接表可以用一個頭結點陣列表示。下面給出其Java實現

public class AdjList {
    private int V = 0;
    private HNode[] adjList =null; //鄰接表
    
    /*表結點*/
     class ArcNode {
        int adjvex, weight;
        ArcNode next;
        
        public ArcNode(int adjvex, int weight) {
            this.adjvex = adjvex;
            this.weight = weight;
            next = null;
        }
    }
    
     /*頭結點*/
    class HNode {
        int vertex;
        ArcNode firstArc;
        
        public HNode(int vertex) {
            this.vertex = vertex;
            firstArc = null;
        }
    }
    
    /*建構函式*/
    public AdjList(int V) {
        this.V = V;
        adjList = new HNode[V+1];
        for(int i = 1; i <= V; i++) {
            adjList[i] = new HNode(i);
        }
    }
    
    /*新增邊*/
    public void addEdge(int start, int end, int weight) {
        ArcNode arc = new ArcNode(end, weight);
        ArcNode temp = adjList[start].firstArc;
        adjList[start].firstArc = arc;
        arc.next = temp;
    }
    
    public int getV() {
        return V;
    }

    public HNode[] getAdjList() {
        return adjList;
    }

}

小頂堆

public class Heap {
    public int size = 0 ;
    public Node[] h = null;     //堆結點
    
    /*記錄Node中vertex對應堆的位置*/
    public int[] index = null;  
    
    /*堆結點:
     * 儲存結點+源點到該結點的距離
     */
    public class Node {
        int vertex, weight;
        
        public Node(int vertex, int weight) {
            this.vertex = vertex;
            this.weight = weight;
        }
    }
    
    public Heap(int maximum) {
        h = new Node[maximum];
        index = new int[maximum];
    }
    
    /*上浮*/
    public void moveUp(int pos) {
        Node temp = h[pos];
        for (; pos > 1 && h[pos/2].weight > temp.weight; pos/=2) {
            h[pos] = h[pos/2];
            index[h[pos].vertex] = pos;  //更新位置
        }
        h[pos] = temp;
        index[h[pos].vertex] = pos;
    }
    
    /*下沉*/
    public void moveDown( ) {
        Node root = h[1];
        int pos = 1, child = 1;
        for(; pos <= size; pos = child) {
            child = 2*pos;
            if(child < size && h[child+1].weight < h[child].weight)
                child++;
            if(h[child].weight < root.weight) {
                h[pos] = h[child];
                index[h[pos].vertex] = pos;
            } else {
                break;
            }
        }
        h[pos] = root;
        index[h[pos].vertex] = pos;
    }
    
    /*插入操作*/
    public void insert(int v, int w) {
        h[++size] = new Node(v, w);
        moveUp(size);
    }
    
    /*刪除堆頂元素*/
    public Node deleteMin( ) {
        Node result = h[1];
        h[1] = h[size--];
        moveDown();
        return result;
    }

}

優化演算法


public class ShortestPath {
    private static final int inf = 0xffffff;
    
    public static void dijkstra(AdjList al) {
        int V = al.getV();
        Heap heap = new Heap(V+1);
        heap.insert(1, 0);
        for(int i = 2; i <= V; i++) {
            heap.insert(i, inf);
        }
        
        for(int k =1; k <= V; k++) {
            Heap.Node min = heap.deleteMin();
            if(min.vertex == V) {
                System.out.println(min.weight);
                break;
            }
            AdjList.ArcNode arc = al.getAdjList()[min.vertex].firstArc;
            while(arc != null) {
                if((min.weight+ arc.weight) < heap.h[heap.index[arc.adjvex]].weight) {
                    heap.h[heap.index[arc.adjvex]].weight = min.weight+ arc.weight;
                    heap.moveUp(heap.index[arc.adjvex]);
                }
                arc = arc.next;
            }
        }
    }
    
    /*main方法用於測試*/
    public static void main(String[] args) {
        AdjList al = new AdjList(5);
        al.addEdge(1, 2, 20);
        al.addEdge(2, 3, 30);
        al.addEdge(3, 4, 20);
        al.addEdge(4, 5, 20);
        al.addEdge(1, 5, 100);
        dijkstra(al);
    }
}

3. 參考資料

相關推薦

深入理解Dijsktra演算法

1. 介紹 Dijsktra演算法是大牛Dijsktra於1956年提出,用來解決有向圖單源最短路徑問題;但是不能解決負權的有向圖,若要解決負權圖則需要用到Bellman-Ford演算法。Dijsktra演算法思想:在DFS遍歷圖的過程中,每一次取出離源點的最近距離的點,將該點標記為已訪問,鬆弛與該點相鄰的結

最小環演算法

做vijos1046時想最小環想了半天,還是沒想出來,到網上一搜原來是用floyd來求…… 最小環 環,,i,j,k三點,環長則為三點 兩兩間距, 且路不能有交點, 所以,我們想到map[i][j] + dist[i][k] + dist[j][k] map記錄最短路,

深度學習深入理解優化器Optimizer演算法(BGD、SGD、MBGD、Momentum、NAG、Adagrad、Adadelta、RMSprop、Adam)

1.http://doc.okbase.net/guoyaohua/archive/284335.html 2.https://www.cnblogs.com/guoyaohua/p/8780548.html   原文地址(英文論文):https://www.cnblogs.c

每日演算法最小邊覆蓋 & 最小路徑覆蓋 & 最小頂點覆蓋 & 最大獨立集 & 最大團

最小邊覆蓋 = 最大獨立集 = |V| - 最大匹配數 這個是在原圖是二分圖上進行的 最小路徑覆蓋和最小邊覆蓋不同,不要求給的圖是二分圖,而是要求是N x N的有向圖,不能有環,然後根據原圖構造二分圖,構造方法是將點一分為二,如,i分為i1和i2然後如果i和j有邊,那麼就在i

最大流之EK演算法與Dinic演算法及最小費用最大流

最大流: 給出一張網路圖,並指定源點和終點,每條邊都有它的容量,起點有著無限的流量,求從源點到經過的所有路徑的最終到達匯點的最大流量和。對於同一個節點,流入的流量之和和流出的流量之和相同,即假如結點1有12流量流入結點2,結點2分別有8流量流入結點3,4流量流入結點4,這種

網絡流總結

hdu 3338 -m ini post 平衡 題目 esp urn data- 【圖論】網絡流總結 最大流部分 網絡流題目的關鍵:看出是網絡流而且確定正確的模型 最大流算法:用來解決從源點s到匯點t,整個網絡最多能輸送多少流量的題目 模

最優貿易

價格 highlight style 不同 相同 -s 存在 n) size [NOIP2009]最優貿易 描述   C 國有 n 個大城市和 m 條道路,每條道路連接這 n 個城市中的某兩個城市。任意兩個城市之間最多只有一條道路直接相連。這 m 條道路中有一部分為單向

Self-Assembly(6-19)

inline const 分析 i++ 不能 hash unbound tac 正方形 [UVA1572]Self-Assembly 算法入門經典第6章6-19(P172) 題目大意:有一些正方形,每條邊上都有A-~Z- A+~Z+的編號,或者00,A+的邊可以拼A-,

深度學習深入理解ReLU(Rectifie Linear Units)激活函數

appdata 稀疏編碼 去掉 ren lock per 作用 開始 href 論文參考:Deep Sparse Rectifier Neural Networks (很有趣的一篇paper) Part 0:傳統激活函數、腦神經元激活頻率研究、稀疏激活性

tarjan

AS code 更新 out 聯通 ace 起點 是什麽 環路 剛接觸tarjan,tarjan其實更多是用來找強聯通分量。我這裏呢,是看qsc的視頻學的。卿學姐講的其實很清楚啦。 我這裏只是做個整理。 low[]:表示能到達這個點的最小編號。[樹枝邊]。啊,其實

割點

百度百科 sum 所有 baidu tdi ++ define show .com 百度百科 Definition&Solution   在一個無向聯通圖中,如果刪除一個點,該圖變得不連通,那麽該點稱作該圖的割點。註意,割點可能不止一個。   對於無向不連通圖

8月19日前填坑指南(自用)

自用 排列 歐拉回路 深度優先 獨立 perf 最小 前向星 最短路 Graph 圖論 前向星 圖的割點、橋 雙連通分量 有向圖的強連通分量 無向圖連通分支 拓撲排序 2-SAT 最短路 第K短路 哈密頓路、歐拉路徑、歐拉回路 DAG的深度優先搜索標記 獨立集、團、支配集

2018國慶三校聯考D5T2

分析: 題意非常醜陋。。。簡化出來就一句話:每個點有選中、未選中兩種狀態,現在給出一些矛盾關係,要求加入儘可能少的矛盾關係,使得沒有合法方案。 如此2sat的模型,顯然需要2sat的連邊方式。。。然後直接列舉每個位置選、不選是否合法即可。若不選合法,則考慮其練的邊是否有一個

[JZOJ5899]NOIP2018模擬10.6資源運輸矩陣樹定理

Description 給定一個n個點,m條邊的帶權無向圖。 定義這個圖的一個生成樹的權值為生成樹上邊權的乘積。 求所有生成樹權值的平均值,答案對998244353取模。 2<=n<=300,n-1<=m<=1000 Solution 平均值=和/總數

深度學習深入理解Batch Normalization批標準化

本文轉載自:郭耀華's Blog https://www.cnblogs.com/guoyaohua/p/8724433.html Batch Normalization作為最近一年來DL的重要成果,已經廣泛被證明其有效性和重要性。雖然有些細節處理還解釋不清其理論原因,但是實踐證明好用才是

半轉載深入理解get和post的區別和一些思考

前言 PHP有道很經典的面試題,請回答get和post的區別,在網上找了很多答案,看到了很多有意思的想法,現在我們一起由淺入深的探討一下~ 下面這個表格是某個講學視訊筆記的講解:   get post

學習筆記深入理解js原型和閉包(3)——prototype原型

既typeof之後的另一位老朋友! prototype也是我們的老朋友,即使不瞭解的人,也應該都聽過它的大名。如果它還是您的新朋友,我估計您也是javascript的新朋友。   在咱們的第一節(深入理解js原型和閉包(1)——一切皆是物件)中說道,函式也是一種物件。他也是屬性的集合,你也可以

學習筆記深入理解js原型和閉包(8)——簡述執行上下文

什麼是“執行上下文”(也叫做“執行上下文環境”)?暫且不下定義,先看一段程式碼: 第一句報錯,a未定義,很正常。第二句、第三句輸出都是undefined,說明瀏覽器在執行console.log(a)時,已經知道了a是undefined,但卻不知道a是10(第三句中)。 在一段js程式碼拿過來真正一句一

學習筆記深入理解js原型和閉包(9)—— 簡述執行上下文

繼續上一篇文章(https://www.cnblogs.com/lauzhishuai/p/10078231.html)的內容。 上一篇我們講到在全域性環境下的程式碼段中,執行上下文環境中有如何資料: 變數、函式表示式——變數宣告,預設賦值為undefined; this——賦值; 函式宣告

學習筆記深入理解js原型和閉包(11)——執行上下文棧

繼續上文的內容。 執行全域性程式碼時,會產生一個執行上下文環境,每次呼叫函式都又會產生執行上下文環境。當函式呼叫完成時,這個上下文環境以及其中的資料都會被消除,再重新回到全域性上下文環境。處於活動狀態的執行上下文環境只有一個。 其實這是一個壓棧出棧的過程——執行上下文棧。如下圖:   可