1. 程式人生 > >Dijkstra演算法(最短路演算法)

Dijkstra演算法(最短路演算法)

迪傑斯特拉演算法是由荷蘭電腦科學家狄克斯特拉於19591959年提出的,因此又叫狄克斯特拉演算法。是從一個頂點到其餘各頂點的最短路徑演算法,解決的是有向圖中最短路徑問題。迪傑斯特拉演算法主要特點是以起始點為中心向外層層擴充套件,直到擴充套件到終點為止。

定義:

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

原理:

1.首先,引入一個輔助向量DD,它的每個分量D_iDi​表示當前所找到的從起始點vv(即源點vv)到其它每個頂點v_ivi​的長度。

例如,D_3=2D3​=2表示從起始點到頂點3的路徑相對最小長度為2。這裡強調相對就是說在演算法執行過程中D的值是在不斷逼近最終結果但在過程中不一定就等於長度。

2.DD的初始狀態為:若從vv到v_ivi​有弧(即從vv到v_ivi​存在連線邊),則D_iDi​為弧上的權值(即為從vv到v_ivi​的邊的權值);否則置D_iDi​為∞∞。

顯然,長度為D_j=min\left \{ v_i \in V\right \}Dj​=min{vi​∈V}的路徑就是從vv出發到頂點v_jvj​的長度最短的一條路徑,此路徑為(v,v_j)(v,vj​)。

3.那麼,下一條長度次短的是哪一條呢?也就是找到從源點vv到下一個頂點的最短路徑長度所對應的頂點,且這條最短路徑長度僅次於從源點vv到頂點v_jvj​的最短路徑長度。

假設該次短路徑的終點是v_kvk​,則可想而知,這條路徑要麼是(v,v_kv,vk​),或者是(v,v_j,v_kv,vj​,vk​)。它的長度或者是從vv到v_kvk​的弧上的權值,或者是D_jDj​加上從v_jvj​到v_kvk​的弧上的權值。

4.一般情況下,假設SS為已求得的從源點vv出發的最短路徑長度的頂點的集合,則可證明:下一條次最短路徑(設其終點為xx)要麼是弧(v,xv,x),或者是從源點vv出發的中間只經過SS中的頂點而最後到達頂點xx的路徑。

因此,下一條長度次短的的最短路徑長度必是D_j=min\left \{ D_i|v_i \in V-S \right \}Dj​=min{Di​∣vi​∈V−S},其中D_iDi​要麼是弧(v,v_i)(v,vi​)上的權值,或者是D_k(v_k∈S)Dk​(vk​∈S)和弧(v_k,v_i)(vk​,vi​)上的權值之和。

演算法描述如下:

1)令arcsarcs表示弧上的權值。若弧不存在,則置arcsarcs為∞∞(在本程式中為MAXCOSTMAXCOST)。SS為已找到的從vv出發的的終點的集合,初始狀態為空集。那麼,從vv出發到圖上其餘各頂點v_ivi​可能達到的長度的初值為D=arcs[Locate Vex(G,v_i)]D=arcs[LocateVex(G,vi​)],v_i∈Vvi​∈V;

2)選擇v_jvj​,使得D_j=min(D|v_i∈V-S)Dj​=min(D∣vi​∈V−S);

3)修改從vv出發的到集合V-SV−S中任一頂點v_kvk​的最短路徑長度。

問題描述:

在無向圖G=(V,E)G=(V,E)中,假設每條邊E_iEi​的長度為w_iwi​,找到由頂點V_0V0​到其餘各點的最短值。

演算法思想:

按路徑長度遞增次序產生演算法:

把頂點集合VV分成兩組:

(1)SS:已求出的頂點的集合(初始時只含有源點V_0V0​)

(2)V-S=TV−S=T:尚未確定的頂點集合

將T中頂點按遞增的次序加入到SS中,保證:

(1)從源點V_0V0​到SS中其他各頂點的長度都不大於從V_0V0​到TT中任何頂點的最短路徑長度

(2)每個頂點對應一個距離值

SS中頂點:從V_0V0​到此頂點的長度

TT中頂點:從V_0V0​到此頂點的只包括SS中頂點作中間頂點的最短路徑長度

依據:可以證明V_0V0​到TT中頂點V_kVk​的,或是從V_0V0​到V_kVk​的直接路徑的權值;或是從V_0V0​經SS中頂點到V_kVk​的路徑權值之和

(反證法可證)

求最短路徑步驟

演算法步驟如下:

G=(V,E)G=(V,E)

1.初始時令S=(V_0)S=(V0​),T=V-S=T=V−S={其餘頂點},TT中頂點對應的距離值

若存在<V_0V0​,V_iVi​>,d(V_0,V_i)d(V0​,Vi​)為<V_0V0​,V_iVi​>弧上的權值

若不存在<V_0V0​,V_iVi​>,d(V_0,V_i)d(V0​,Vi​)為∞∞

2.從TT中選取一個與SS中頂點有關聯邊且權值最小的頂點WW,加入到SS中

3.對其餘TT中頂點的距離值進行修改:若加進WW作中間頂點,從V_0V0​到V_iVi​的距離值縮短,則修改此距離值

重複上述步驟2、3,直到SS中包含所有頂點,即W=V_iW=Vi​為止

1.0版標程:

#include<cstdio>
using namespace std;
int n,m,s,tot,dis[10003],head[500003],mi,t;
bool vis[10003];
struct edge {
    int next,to,w;
    edge() {
        this->next=this->to=this->w=0;
    }
} h[500003];
void add(int u,int v,int w) {
    h[++tot].next=head[u];
    h[tot].to=v;
    h[tot].w=w;
    head[u]=tot;
}
void dijkstra() {
    for(int i=1; i<=n; i++) {
        dis[i]=2147483647;
    }
    dis[s]=0;
    for(int i=1; i<=n; i++) {
        mi=2147483647;
        t=-1;
        for(int j=1; j<=n; j++) {
            if(!vis[j]&&dis[j]<mi) {
                mi=dis[j];
                t=j;
            }
        }
        if(t==-1) {
            break;
        }
        vis[t]=1;
        for(int j=head[t]; j; j=h[j].next) {
            if(!vis[h[j].to]&&dis[h[j].to]>(h[j].w+dis[t])) {
                dis[h[j].to]=h[j].w+dis[t];
            }
        }
    }
}
int main() {
    scanf("%d%d%d",&n,&m,&s);
    for(int i=0; i<m; i++) {
        int u,v,w;
        scanf("%d%d%d",&u,&v,&w);
        add(u,v,w);
    }
    dijkstra();
    for(int i=1; i<=n; i++) {
        printf("%d ",dis[i]);
    }
    return 0;
}

堆優化:

思考:

該演算法複雜度為n^2n2,我們可以發現,如果邊數遠小於n^2n2,對此可以考慮用堆這種資料結構進行優化,取出最短路徑的複雜度降為O(1)O(1);每次調整的複雜度降為O(elogn)O(elogn);ee為該點的邊數,所以複雜度降為O((m+n)logn)O((m+n)logn)。

實現:

1.將與源點相連的點加入堆,並調整堆。

2.選出堆頂元素uu(即代價最小的元素),從堆中刪除,並對堆進行調整。

3.處理與uu相鄰的,未被訪問過的,滿足三角不等式的頂點

1):若該點在堆裡,更新距離,並調整該元素在堆中的位置。

2):若該點不在堆裡,加入堆,更新堆。

4.若取到的uu為終點,結束演算法;否則重複步驟2、3。

2.0版標程:

#include<cstdio>
#include<queue>
using namespace std;
int head[100003],d[100003],ver[200003],edge[200003],Next[200003],n,m,tot,x,i,y,z,s,w,u,k;
bool v[100003];
priority_queue<pair<int,int> >q;
inline void add(int x,int y,int z) {
    ver[++tot]=y;
    edge[tot]=z;
    Next[tot]=head[x];
    head[x]=tot;
}
int main() {
    scanf("%d%d%d",&n,&m,&s);
    while(m--) {
        scanf("%d%d%d",&u,&k,&w);
        add(u,k,w);
    }
    for(i=1; i<=n; ++i) {
        d[i]=2147483647;
    }
    d[s]=0;
    q.push(make_pair(0,s));
    while(q.size()) {
        x=q.top().second;
        q.pop();
        if(v[x]) {
            continue;
        }
        v[x]=1;
        for(i=head[x]; i; i=Next[i]) {
            y=ver[i];
            z=edge[i];
            if(d[y]>d[x]+z) {
                d[y]=d[x]+z;
                q.push(make_pair(-d[y],y));
            }
        }
    }
    for(i=1; i<=n; ++i) {
        printf("%d ",d[i]);
    }
}