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

單源最短路徑

鎖定
給定一個帶權有向圖G=(V,E),其中每條邊的權是一個實數。另外,還給定V中的一個頂點,稱為源。要計算從源到其他所有各頂點的最短路徑長度。這裏的長度就是指路上各邊權之和。這個問題通常稱為單源最短路徑 [1]  問題。
中文名
單源最短路徑
適用領域
算法 圖論 ACM比賽
主要方法
Dijkstra Bellman-Ford SPFA

單源最短路徑Dijkstra算法

單源最短路徑問題描述

給定一個帶權有向圖 G=(V,E) ,其中每條邊的權是一個非負實數。另外,還給定 V 中的一個頂點,稱為源。我們要計算從源到所有其他各頂點的最短路徑長度。這裏的長度是指路上各邊權之和。這個問題通常稱為單源最短路徑問題。

單源最短路徑解決方案

Dijkstra提出按各頂點與源點v間的路徑長度的遞增次序,生成到各頂點的最短路徑的算法。既先求出長度最短的一條最短路徑,再參照它求出長度次短的一條最短路徑,依次類推,直到從源點v 到其它各頂點的最短路徑全部求出為止。

單源最短路徑解題思想

將圖G中所有的頂點V分成兩個頂點集合S和T。以v為源點已經確定了最短路徑的終點併入S集合中,S初始時只含頂點v,T則是尚未確定到源點v最短路徑的頂點集合。然後每次從T集合中選擇S集合點中到T路徑最短的那個點,並加入到集合S中,並把這個點從集合T刪除。直到T集合為空為止。
具體步驟
1、選一頂點v為源點,並視從源點v出發的所有邊為到各頂點的最短路徑(確定數據結構:因為求的是最短路徑,所以①就要用一個記錄從源點v到其它各頂點的路徑長度數組dist[],開始時,dist是源點v到頂點i的直接邊長度,即dist中記錄的是鄰接陣的第v行。②設一個用來記錄從源點到其它頂點的路徑數組path[],path中存放路徑上第i個頂點的前驅頂點)。
2、在上述的最短路徑dist[]中選一條最短的,並將其終點(即<v,k>)k加入到集合s中。
3、調整T中各頂點到源點v的最短路徑。 因為當頂點k加入到集合s中後,源點v到T中剩餘的其它頂點j就又增加了經過頂點k到達j的路徑,這條路徑可能要比源點v到j原來的最短的還要短。調整方法是比較dist[k]+g[k,j]與dist[j],取其中的較小者。
4、再選出一個到源點v路徑長度最小的頂點k,從T中刪去後加入S中,再回去到第三步,如此重複,直到集合S中的包含圖G的所有頂點。

單源最短路徑示例代碼

pascal:
PROCEDURE DIJKSTRA;
VAR
DIST:ARRAY[1..MAXP]OF LONGINT;{距離數組,記錄目前從源點出發已經找到的最短路徑長度}
VISITED:ARRAY[1..MAXP]OF BOOLEAN;{標誌數組,記錄是否已經找到了某一點的最終最短路徑}
I,J,MIN,MINP:LONGINT;
BEGIN
FILLCHAR(VISITED,SIZEOF(VISITED),FALSE);{初始化源點和路徑數組}
FOR I:=1 TO MAXP DO
BEGIN
DIST:=MAP[SOURCE,I];
IF DIST<M THEN
PATH:=SOURCE
ELSE
PATH:=0;
END;{源點的最短路徑肯定是不用找的...}
VISITED[SOURCE]:=TRUE;
{DIJKSTRA的思想是按遞增長度來產生路徑,每次選出當前已經
找到的最短的一條路徑,它必然是一條最終的最短路徑.因此
每次找出當前距離數組中最小的,必然是一條最終的最短路徑}
FOR I:=2 TO MAXP DO
BEGIN
MIN:=INFINITY;
MINP:=0;
FOR J:=1 TO MAXP DO
IF (NOTVISITED[J]) AND (DIST[J]<MIN) THEN
BEGIN
MIN:=DIST[J];
MINP:=J;
END;{已找到源點到點MINP的最短路徑,做上標誌}
VISITED[MINP]:=TRUE;{修改距離數組}
FOR J:=1 TO MAXP DO
IF (NOTVISITED[J]) AND (DIST[MINP]+MAP[MINP,J]<DIST[J]) THEN
BEGIN
DIST[J]:=DIST[MINP]+MAP[MINP,J];
PATH[J]:=MINP;
END;
END;
END;
c++:
//Compute shortest path distances from s,return them in D
void Dijkstra(Graph *G,int *D,int s){ //這裏的s是指計算最小路徑的源,但是題目中沒有用到,應該加一個
//初始化數組D的函數
/*
for(int i =0; i<G->n() ; i++){//僅供參考(本來這個初始化應該在傳入時候就做好的,但是為了符合這個函數)
if(i ==s ) D[i] =0;
else D[i]=INFINITY;
}
*/
int i,v,w;
for(i=0;i<G->n();i++){ //Process the vertices
v=minVertex(G,D);
if(D[v]==INFINITY) return; //Unreachable vertices
G->setMark(v,VISITED);
for(w=G->first(v);w<G->n();w=G->nexr(v,w))
if(D[w]>(D[v]+G->weight(v,w)))
D[w]=D[v]+G->weight(v,w);
}
}
int minVertex(Graph * G, int * D){//找出最小的點
int i , v ;
for(i=0; i<G->n;i++){ //找沒有被訪問的點
if(G->getMark(i)== UNVISITED){
v=i; break;
}
}
for(i++; i<G->n;i++){ //找比上面還小的未被訪問的點(注意此時的i++)
if((G->getMark(i)== UNVISITED)&&D[i]<D[v]))
v=i;
return v;
}
//還有Graph類沒有給出
c:
void dijkstra(int s,WItem dist[],int prev[],Graph G)

 {

 int i,j;

 List L=Listinit();

 if(s<1||s>G->n)

 Error("Out of bounds");

 /*初始化 dist,prev和L*/

 for(i=1;1<=G->n;i++)

 {

 dist[i]=G->a[s][i];

 if(dist[i]==G->NoEdge)

 prev[i]=0;

 else{

 prev[i]=s;

 ListInsert(0,i,L);

 }

 }

 dist[s]=0;

 /*修改dist和prev*/

 
while
(!ListEmpty(L))

 {

 /*找L中具有最小dist值的頂點*./

 /*將頂點V從表L中刪除,並修改dist的值*/

 i=ListDelMin(L,dist);

 for(j=1;j<=G->n;j++)

 {

 if(G->a[i][j]!=G->NoEdge&&(!prev[j]||dist[j]>dist[j]+G->a[i][j]))

 {

 dist[j]=dist[i]+G->a[i][j];

 if(!prev[j])

 ListInsert(0,j,L);

 prev[]=i;

 }

 }

 }

 }

單源最短路徑Bellman-Ford算法

單源最短路徑描述

由於Dijikstra算法對於帶負邊權的圖就無能為力了,但是Bellman-Ford算法就可以解決這個問題。

單源最短路徑解題思想

算法基於動態規劃,反覆用已有的邊來更新最短距離,Bellman-Ford算法的核心思想是鬆弛。如果dist[u]和dist[v]滿足dist[v]>dist[u]+map[u][v],dist[v]就應該被更新為dist[u]+map[u][v]。反覆的利用上式對dist數組進行鬆弛,如果沒有負權迴路的話,應當會在n-1次鬆弛後結束。

單源最短路徑偽代碼

s為源,map[][]記錄圖的信息,map[u][v]為點u到v的邊的長度,結果保存在dist[];
  1. 初始化,所有點 i 賦初值dist[i] =+無窮大,出發點為s,dist[s]=0;
  2. 對於每條邊(u,v),如果dist[u]!=+無窮大且dist[v]>dist[u]+map[u][v],則dist[v]=dist[u]+map[u][v].
  3. 循環步驟2n-1次或直到某次中不在更新,進入步驟4.
  4. 對於每條邊(u,v),如果dist[u]!=+無窮大且dist[v]>dist[u]+map[u][v],則存在負權迴路。
c:
bool bellmanFord(int s,int head[maxn],NODE edge[maxn],int dist[maxn])
{
    for(int i =0; i < n; i++) dist[i] = INF;
    dist[0] = 0;
    for(int i = 0; i < n-1; i++)
    {
        for(int j = 0; j < n; j++)
        {
            if(dist[j] == INF) continue;
            for(int k = head[j];k != -1; k = edge[k].next)
            {
                if(edge[k].w != INF&&dist[edge[k].to] > dist[j] + edge[k].w)
                dist[edge[k].to] = dist[j] + edge[k].w;
            }
        }
    }
    for(int j = 0; j < n; j++)
    {
        if(dist[j] == INF) continue;
        for(k = head[j]; k != -1;k = edge[k].next)
            if(edge[k].w != INF&&dist[edge[k].to] > dist[j] + edge[k].w)
        return false;
    }
    return true;
}

單源最短路徑SPFA算法

單源最短路徑描述

SPFA(Shortest Path Faster Algorithm)算法是求單源最短路徑的一種算法,在Bellman-ford算法的基礎上加上一個隊列優化,減少了冗餘的鬆弛操作,是一種高效的最短路算法。在 NOI2018Day1T1歸程 中正式被卡,其時間複雜度為O(nm),遠劣於Dijkstra的O((n+m)log m)。

單源最短路徑解題思想

我們約定加權有向圖G不存在負權迴路,即最短路徑一定存在。如果某個點進入隊列的次數超過N次則存在負環(SPFA無法處理帶負環的圖)。當然,我們可以在執行該算法前做一次拓撲排序,以判斷是否存在負權迴路,但這不是我們討論的重點。我們用數組d記錄每個結點的最短路徑估計值,而且用鄰接表來存儲圖G。我們採取的方法是動態逼近法:設立一個先進先出的隊列用來保存待優化的結點,優化時每次取出隊首結點u,並且用u點當前的最短路徑估計值對離開u點所指向的結點v進行鬆弛操作,如果v點的最短路徑估計值有所調整,且v點不在當前的隊列中,就將v點放入隊尾。這樣不斷從隊列中取出結點來進行鬆弛操作,直至隊列空為止。定理: 只要最短路徑存在,上述SPFA算法必定能求出最小值。

單源最短路徑偽代碼

SPFA實際上是Bellman-Ford基礎上的隊列優化
SPFA(G,w,s)
1. INITIALIZE-SINGLE-SOURCE(G,s)
2. INITIALIZE-QUEUE(Q)
3. ENQUEUE(Q,s)
4. While Not EMPTY(Q)
5. Do u<-DLQUEUE(Q)
6. For 每條邊(u,v) in E[G]
7. Do tmp<-d[v]
8. Relax(u,v,w)
9. If (d[v] < tmp) and (v不在Q中)
10. ENQUEUE(Q,v)
c++:
bool SPFA(intsource){
  deque<int>dq;
  int i,j,x,to;
  for(i=1;i<=nodesum;i++){
    in_sum[i]=0;
    in_queue[i]=false;
    dist[i]=INF;
    path[i]=-1;
  }
  dq.push_back(source);
  in_sum[source]++;
  dist[source]=0;
  in_queue[source]=true;//初始化完成
  while(!dq.empty()){
    x=dq.front();
    dq.pop_front();
    in_queue[x]=false;
    for(i=0;i<adjmap[x].size();i++){
      to=adjmap[x][i].to;
      if((dist[x]<INF)&&(dist[to]>dist[x]+adjmap[x][i].weight)){
        dist[to]=dist[x]+adjmap[x][i].weight;
        path[to]=x;
        if(!in_queue[to]){
          in_queue[to]=true;
          in_sum[to]++;
          if(in_sum[to]==nodesum)
            return false;
          if(!dq.empty()){
            if(dist[to]>dist[dq.front()])
              dq.push_back(to);
            else dq.push_front(to);
          }else
            dq.push_back(to);
        }
      }
    }
  }
  return true;
}
參考資料
  • 1.    馮林 金博 於瑞雲.圖論及應用:哈爾濱工業大學出版社,2012年:78-85