最短路演算法-迪傑斯特拉演算法
1.圖中的最短路徑求解的演算法花樣百出,但是最常用的就是那幾種很耳熟能詳的演算法。今天我們來了解一下十分常用的迪傑斯特拉演算法。
2.dj是一種求非負權值的單源最短路演算法。通俗的講就是求這樣的問題:在圖中的兩個點s,t並且這個圖中沒有負的邊權,那麼求解從s到t的最短的路徑權值和是多少。首先說明幾個特點,注意dj的使用範圍,非負權值的單源最短路。但是雖然我們求的是s到t的最短路,但是求解過程中會把s到圖中所有點的最短路都求出來。這是dj的一些特性,為什麼會有這樣的特性,我們來看一下原理就明白了。
3.dj演算法的原理:首先看圖
假設我們要求0到4的最短路,那麼其實dj是這樣執行的,它的基本思想就是貪心和鬆弛。他首先把點分為兩個集合s和t,s代表已經求得的最短路徑的點,t代表還未求得最短路的點。初始位置s裡面只有源點,也就是0其他節點都在t裡。然後會有一個dis陣列,儲存當前狀態下源點到各個節點的最短路,dis初始化的結果就是源點到各個點之間的路徑長度,如果有直接的連邊,就是這個連邊的長度,如果沒有就是INF,例如上圖中我們可以知道dis[1]=10,dis[4]=100,dis[2]=inf。然後就可以開始工作了。工作的流程是這樣的,首先我們從源點出發,找到距離最近的點(貪心),沒錯,我們在這個圖中找到的就是10,找到之後更新dis對應的值(當然這裡的更新是沒有意義的,因為是一樣的),然後我們鬆弛這是很重要的,或者說這就是dj的精髓。怎麼鬆弛呢?是這樣工作的,我們現在已經有了1點,1被加入s集合中,我們通過1可以到達2,並且路徑的長度是50,所以0到達2的路徑長度就是60,因為之前0到2是沒有直接的路徑的,所以我們這個時候就可以把路徑更新到60,這樣就完成了鬆弛。當然了,dj並不僅僅是這麼運作的,當找到1之後,他會計算髮現到2的長度是60,但是到3的長度是30(這裡的到指的是源點到其他點),所以他會選擇小的去,所以第二個被加入的節點是3而不是2(這也是一種貪心的策略)。然後這個時候我們依然使用上邊的方法,我們發現這個時候可以到達的點有2,4,但是到達4的最短路徑是90,到達2的是50(通過3來鬆弛),這個時候2倍加入s集合,2被加入s集合之後,我們又可以通過2來鬆弛4,所以到4的最短路徑就是60了。這樣所有點被加入集合。最短路演算法完成。
4.疑惑的解決:開始的時候我們就提出了兩個問題。1.dj是基與非負權值的。2.dj雖然是求兩個點之間的最短路,但是會把源點到其他所有點的最短路都求出來。現在看完原理之後我想就很好回答這個問題了,首先為什麼是非負權值,因為dj的鬆弛過程決定的,如果出現負邊,那麼鬆弛的時候就會在哪個具有負邊的地方迴圈。其次,雖然是求兩點之間的最短路,但是任何一點的加入都有可能會對這兩點之間的最短路產生鬆弛。所以我們需要把所有的點都加進去試一遍。
5.dj的複雜度是n方的,這個與圖是怎麼存的沒有關係。下面給出實現的程式碼:
/*Dijkstra求單源最短路徑 */ #include <iostream> #include<stack> #define M 100 #define N 100 using namespace std; typedef struct node { int matrix[N][M]; //鄰接矩陣 int n; //頂點數 int e; //邊數 }MGraph; void DijkstraPath(MGraph g,int *dist,int *path,int v0) //v0表示源頂點 { int i,j,k; bool *visited=(bool *)malloc(sizeof(bool)*g.n); for(i=0;i<g.n;i++) //初始化 { if(g.matrix[v0][i]>0&&i!=v0) { dist[i]=g.matrix[v0][i]; path[i]=v0; //path記錄最短路徑上從v0到i的前一個頂點 } else { dist[i]=INT_MAX; //若i不與v0直接相鄰,則權值置為無窮大 path[i]=-1; } visited[i]=false; path[v0]=v0; dist[v0]=0; } visited[v0]=true; for(i=1;i<g.n;i++) //迴圈擴充套件n-1次 { int min=INT_MAX; int u; for(j=0;j<g.n;j++) //尋找未被擴充套件的權值最小的頂點 { if(visited[j]==false&&dist[j]<min) { min=dist[j]; u=j; } } visited[u]=true; for(k=0;k<g.n;k++) //更新dist陣列的值和路徑的值 { if(visited[k]==false&&g.matrix[u][k]>0&&min+g.matrix[u][k]<dist[k]) { dist[k]=min+g.matrix[u][k]; path[k]=u; } } } } void showPath(int *path,int v,int v0) //列印最短路徑上的各個頂點 { stack<int> s; int u=v; while(v!=v0) { s.push(v); v=path[v]; } s.push(v); while(!s.empty()) { cout<<s.top()<<" "; s.pop(); } } int main(int argc, char *argv[]) { int n,e; //表示輸入的頂點數和邊數 while(cin>>n>>e&&e!=0) { int i,j; int s,t,w; //表示存在一條邊s->t,權值為w MGraph g; int v0; int *dist=(int *)malloc(sizeof(int)*n); int *path=(int *)malloc(sizeof(int)*n); for(i=0;i<N;i++) for(j=0;j<M;j++) g.matrix[i][j]=0; g.n=n; g.e=e; for(i=0;i<e;i++) { cin>>s>>t>>w; g.matrix[s][t]=w; } cin>>v0; //輸入源頂點 DijkstraPath(g,dist,path,v0); for(i=0;i<n;i++) { if(i!=v0) { showPath(path,i,v0); cout<<dist[i]<<endl; } } } return 0; }