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]);
}
}