複製鏈接
請複製以下鏈接發送給好友

迪克斯特拉算法

鎖定
迪傑斯特拉算法(Dijkstra)是由荷蘭計算機科學家狄克斯特拉於1959年提出的,因此又叫狄克斯特拉算法。是從一個頂點到其餘各頂點的最短路徑算法,解決的是有權圖中最短路徑問題。迪傑斯特拉算法主要特點是從起始點開始,採用貪心算法策略,每次遍歷到始點距離最近且未訪問過的頂點的鄰接節點,直到擴展到終點為止。 [1] 
中文名
迪克斯特拉算法 [1] 
外文名
Dijkstra's Algorithm [2] 
別    名
狄克斯特拉算法 [1] 
分    類
計算機算法 [1] 
用    途
單源最短路徑問題 [1] 
簡    稱
Dij算法 [1] 

迪克斯特拉算法定義

Dijkstra算法一般的表述通常有兩種方式,一種用永久和臨時標號方式,一種是用OPEN, CLOSE表的方式,這裏均採用永久和臨時標號的方式。注意該算法要求圖中不存在負權邊。 [2] 

迪克斯特拉算法原理

圖示 圖示
1.首先,引入一個輔助數組(vector)D,它的每個元素D
表示當前所找到的從起始點
(即源點
)到其它每個頂點
的長度。 [3] 
例如,D[3] = 2表示從起始點到頂點3的路徑相對最小長度為2。 [1]  這裏強調相對就是説在算法執行過程中D的值是在不斷逼近最終結果但在過程中不一定就等於長度。 [3] 
2.D的初始狀態為:若從
有弧(即從
存在連接邊),則D
為弧上的權值(即為從
的邊的權值);否則置D
為∞。 [1] 
顯然,長度為 D
= Min{ D |
∈V } 的路徑就是從
出發到頂點
的長度最短的一條路徑,此路徑為(
)。 [3] 
3.那麼,下一條長度次短的是哪一條呢?也就是找到從源點
到下一個頂點的最短路徑長度所對應的頂點,且這條最短路徑長度僅次於從源點
到頂點
的最短路徑長度。 [1] 
假設該次短路徑的終點是
,則可想而知,這條路徑要麼是(
),或者是(
)。它的長度或者是從
的弧上的權值,或者是D
加上從
的弧上的權值。 [3] 
4.一般情況下,假設S為已求得的從源點
出發的最短路徑長度的頂點的集合,則可證明:下一條次最短路徑(設其終點為
)要麼是弧(
),或者是從源點
出發的中間只經過S中的頂點而最後到達頂點
的路徑。 [1] 
因此,下一條長度次短的的最短路徑長度必是D
= Min{ D
|
∈V-S },其中D
要麼是弧(
)上的權值,要麼是D
(
∈S)和弧(
,
)上的權值之和。 [1] 
算法描述如下:
1)令arcs表示弧上的權值。若弧不存在,則置arcs為∞(在本程序中為MAXCOST)。S為已找到的從
出發的的終點的集合,初始狀態為空集。那麼,從
出發到圖上其餘各頂點
可能達到的長度的初值為D=arcs[Locate Vex(G,
)],
∈V; [1] 
2)選擇
,使得D
=Min{ D |
∈V-S } ; [1] 
3)修改從
出發的到集合V-S中任一頂點
的最短路徑長度。 [2] 

迪克斯特拉算法問題描述

有向圖 G=(V,E) 中,假設每條邊 E[i] 的長度為 w[i],找到由頂點 V0 到其餘各點的最短值。 [1] 

迪克斯特拉算法算法思想

按路徑長度遞增次序產生算法: [1] 
把頂點集合V分成兩組: [3] 
(1)S:已求出的頂點的集合(初始時只含有源點V0) [1] 
(2)V-S=T:尚未確定的頂點集合 [1] 
將T中頂點按遞增的次序加入到S中,保證: [1] 
(1)從源點V0到S中其他各頂點的長度都不大於從V0到T中任何頂點的最短路徑長度 [1] 
(2)每個頂點對應一個距離值 [2] 
S中頂點:從V0到此頂點的長度 [1] 
T中頂點:從V0到此頂點的只包括S中頂點作中間頂點的最短路徑長度 [1] 
依據:可以證明V0到T中頂點Vk的,或是從V0到Vk的直接路徑的權值;或是從V0經S中頂點到Vk的路徑權值之和 [1] 
反證法可證)
求最短路徑步驟 [1] 
算法步驟如下: [1] 
G={V,E}
1. 初始時令 S={V0},T=V-S={其餘頂點},T中頂點對應的距離值 [1] 
若存在,d(V0,Vi)為弧上的權值 [1] 
若不存在,d(V0,Vi)為∞ [2] 
2. 從T中選取一個與S中頂點有關聯邊且權值最小的頂點W,加入到S中 [1] 
3. 對其餘T中頂點的距離值進行修改:若加進W作中間頂點,從V0到Vi的距離值縮短,則修改此距離值 [1] 
重複上述步驟2、3,直到S [1]  中包含所有頂點,即W=Vi為止 [1] 

迪克斯特拉算法算法實現

pascal語言
下面是該算法的Pascal程序
type
bool=array[1..10]ofboolean;
arr=array[0..10]ofinteger;
var
a:array[1..10,1..10]ofinteger;//存儲圖的鄰接數組,無邊為10000
c,d,e:arr;//c為最短路徑數值,d為各點前趨,
t:bool;//e:路徑,t為輔助數組
i,j,n,m:integer;
inf,outf:text;
procedure init;//不同題目鄰接數組建立方式不一樣
begin
    assign(inf,inputfile);
    assign(outf,outputfile);
    reset(inf);
    rewrite(outf);
    read(inf,n);
    for i:= 1 to n do
    begin
        for j := 1 to n do
        begin
            read(inf,a[i,j]);
            if a[i,j]=0 then
                a[i,j]:=10000;
            end;
        end;
    end;
procedure dijkstra(qi:integer;t:bool;varc{,d}:arr);
//qi起點,{}中為求路徑部分,不需求路徑時可以不要
var
i,j,k,min:integer;
begin
    t[qi]:=true;
//t數組一般在調用前初始,除起點外所有節點都化成false,也可將部分點初始化成true以迴避這些點
    for i:= 1 to n do
        d[i] := qi;
    d[qi]:=0;
    for i:=1 to n do
        c[i]:=a[qi,i];
    for i:= 1 to n-1 do
    begin
        min:=maxint;//改為最大值
        for j:=1 to n do
            if(c[j]<min) and not t[j] then
            begin
                k:=j;
                min:=c[j];
            end;
        t[k]:=true;
        for j:=1 to n do
            if(c[k]+a[k,j]<c[j]) and not t[j] then
            begin
                c[j]:=c[k]+a[k,j];
                d[j]:=k;
            end;
    end;
end;
        
procedure make(zh:integer;d:arr;vare:arr);//生成路徑,e[0]保存路徑
var
i,j,k:integer;//上的節點個數
begin
    i:=0;
    while d[zh]<>0 do
    begin
        inc(i);
        e[i]:=zh;
        zh:=d[zh];
    end;
    inc(i);
    e[i]:=qi;
    e[0]:=i;
end;
主程序調用:求長度:初始化t,然後dijkstra(qi,t,c,d) [1] 
求路徑:make(m,d,e) ,m是終點 [2] 
C語言
下面是該算法的C語言實現 [1] 
#include<stdio.h>
#include<stdlib.h>
#define max1 10000000  //原詞條這裏的值太大,導致溢出,後面比較大小時會出錯
int a[1000][1000];
int d[1000];//d表示源節點到該節點的最小距離
int p[1000];//p標記訪問過的節點
int i, j, k;
int m;//m代表邊數
int n;//n代表點數
int main()
{
    scanf("%d%d",&n,&m);
    int    min1;
    int    x,y,z;
    for(i=1;i<=m;i++)
    {
        scanf("%d%d%d",&x,&y,&z);
        a[x][y]=z;
        a[y][x]=z;
    }
    for( i=1; i<=n; i++)
        d[i]=max1;
    d[1]=0;
    for(i=1;i<=n;i++)
    {
        min1 = max1;
        //下面這個for循環的功能類似冒泡排序,目的是找到未訪問節點中d[j]值最小的那個節點,
        //作為下一個訪問節點,用k標記
        for(j=1;j<=n;j++)
            if(!p[j]&&d[j]<min1)
            {
                min1=d[j];
                k=j;
            }
        //p[k]=d[k]; // 這是原來的代碼,用下一 條代碼替代。初始時,執行到這裏k=1,而d[1]=0
       //從而p[1]等於0,這樣的話,上面的循環在之後的每次執行之後,k還是等於1。
        p[k] = 1; //置1表示第k個節點已經訪問過了
        for(j=1;j<=n;j++)
            if(a[k][j]!=0&&!p[j]&&d[j]>d[k]+a[k][j])
                d[j]=d[k]+a[k][j];
    }
    //最終輸出從源節點到其他每個節點的最小距離
    for(i=1;i<n;i++)
        printf("%d->",d[i]);
    printf("%d\n",d[n]); 
    return 0;
}

大學經典教材<<數據結構>>(C語言版 嚴蔚敏 吳為民 編著) 中該算法的實現 [1] 
/*
測試數據 教科書 P189 G6 的鄰接矩陣 其中 數字 1000000 代表無窮大
6
1000000 1000000 10 100000 30 100
1000000 1000000 5 1000000 1000000 1000000
1000000 1000000 1000000 50 1000000 1000000
1000000 1000000 1000000 1000000 1000000 10
1000000 1000000 1000000 20 1000000 60
1000000 1000000 1000000 1000000 1000000 1000000
結果:
D[0]   D[1]   D[2]   D[3]   D[4]   D[5]
 0   1000000   10     50     30     60
*/
#include <iostream>
#include <cstdio>
#define MAX 1000000
using namespace std;
int arcs[10][10];//鄰接矩陣
int D[10];//保存最短路徑長度
int p[10][10];//路徑
int final[10];//若final[i] = 1則説明 頂點vi已在集合S中
int n = 0;//頂點個數
int v0 = 0;//源點
int v,w;
void ShortestPath_DIJ()
{
     for (v = 0; v < n; v++) //循環 初始化
     {
          final[v] = 0; D[v] = arcs[v0][v];
          for (w = 0; w < n; w++) p[v][w] = 0;//設空路徑
          if (D[v] < MAX) {p[v][v0] = 1; p[v][v] = 1;}
     }
     D[v0] = 0; final[v0]=1; //初始化 v0頂點屬於集合S
     //開始主循環 每次求得v0到某個頂點v的最短路徑 並加v到集合S中
     for (int i = 1; i < n; i++)
     {
          int min = MAX;
          for (w = 0; w < n; w++)
          {
               //我認為的核心過程--選點
               if (!final[w]) //如果w頂點在V-S中
               {
                    //這個過程最終選出的點 應該是選出當前V-S中與S有關聯邊
                    //且權值最小的頂點 書上描述為 當前離V0最近的點
                    if (D[w] < min) {v = w; min = D[w];}
               }
          }
          final[v] = 1; //選出該點後加入到合集S中
          for (w = 0; w < n; w++)//更新當前最短路徑和距離
          {
               /*在此循環中 v為當前剛選入集合S中的點
               則以點V為中間點 考察 d0v+dvw 是否小於 D[w] 如果小於 則更新
               比如加進點 3 則若要考察 D[5] 是否要更新 就 判斷 d(v0-v3) + d(v3-v5) 的和是否小於D[5]
               */
               if (!final[w] && (min+arcs[v][w]<D[w]))
               {
                    D[w] = min + arcs[v][w];
                   // p[w] = p[v];
                    p[w][w] = 1; //p[w] = p[v] + [w]
               }
          }
     }
}


int main()
{
    cin >> n;
    for (int i = 0; i < n; i++)
    {
         for (int j = 0; j < n; j++)
         {
              cin >> arcs[i][j];
         }
    }
    ShortestPath_DIJ();
    for (int i = 0; i < n; i++) printf("D[%d] = %d\n",i,D[i]);
    return 0;
}

迪克斯特拉算法堆優化

思考
算法複雜度為n^2,我們可以發現,如果邊數遠小於n^2,對此可以考慮用這種數據結構進行優化,取出最短路徑的複雜度降為O(1);每次調整的複雜度降為O(elogn);e為該點的邊數,所以複雜度降為O((m+n)logn)。 [2] 
實現
1. 將源點加入堆,並調整堆。 [2] 
2. 選出堆頂元素u(即代價最小的元素),從堆中刪除,並對堆進行調整。 [4] 
3. 處理與u相鄰的,未被訪問過的,滿足三角不等式的頂點 [4] 
1):若該點在堆裏,更新距離,並調整該元素在堆中的位置。 [4] 
2):若該點不在堆裏,加入堆,更新堆。 [4] 
4. 若取到的u為終點,結束算法;否則重複步驟2、3。 [4] 
Java代碼
//假設起點為src, 終點為dst, 圖以二維矩陣的形式存儲,若graph[i][j] == 0, 代表i,j不相連    
//visit[i] == 0,代表未訪問,visit[0] == -1代表已訪問    
public int Dijkstra(int src, int dst, int[][] graph,int[] visit){
    //節點個數        
    int n = graph.length;        
    PriorityQueue<Node> pq = new PriorityQueue<>(new Node());        
    //將起點加入pq        
    pq.add(new Node(src, 0));        
    while (!pq.isEmpty()){            
        Node t = pq.poll();            
        //當前節點是終點,即可返回最短路徑            
        if(t.node == dst)                
            return t.cost;            
        //t節點表示還未訪問            
        if (visit[t.node]==0){                
            //將節點設置為已訪問                
            visit[t.node] = -1;                
            //將當前節點相連且未訪問的節點遍歷                
            for (int i = 0; i < n; i++) {                    
                if (graph[t.node][i]!=0 && visit[i]==0) {                        
                    pq.add(new Node(i, t.cost + graph[t.node][i]));                    
                }                
            }            
        }        
    }        
    return -1;    
}    
//定義一個存儲節點和離起點相應距離的數據結構    
class Node implements Comparator<Node> {        
    public int node;        
    public int cost;
            
    public Node(){}
    
    public Node(int node, int cost){            
        this.node = node;            
        this.cost = cost;        
    }        
    @Override        
    public int compare(Node node1, Node node2){            
        return node1.cost-node2.cost;       
    }    
}
參考資料