最短路徑 之 SPFA演算法

2022-08-03 09:36:10 字數 2176 閱讀 9858

求最短路徑的演算法有許多種,除了排序外,恐怕是oi界中解決同一類問題演算法最多的了。最熟悉的無疑是dijkstra,接著是bellman-ford,它們都可以求出由乙個源點向其他各點的最短路徑;如果我們想要求出每一對頂點之間的最短路徑的話,還可以用floyd-warshall。

spfa是這篇日誌要寫的一種演算法,它的效能非常好,**實現也並不複雜。特別是當圖的規模大,用鄰接矩陣存不下的時候,用spfa則可以很方便地面對臨接表。每個人都寫過廣搜,spfa的實現和廣搜非常相似。

如何求得最短路徑的長度值?

首先說明,spfa是一種單源最短路徑演算法,所以以下所說的「某點的最短路徑長度」,指的是「某點到源點的最短路徑長度」。

我們記源點為s,由源點到達點i的「當前最短路徑」為d[i],開始時將所有d[i]初始化為無窮大,d[s]則初始化為0。演算法所要做的,就是在執行過程中,不斷嘗試減小d陣列的元素,最終將其中每乙個元素減小到實際的最短路徑。

過程中,我們要維護乙個佇列,開始時將源點置於隊首,然後反覆進行這樣的操作,直到隊列為空:

(1)從隊首取出乙個結點u,掃瞄所有由u結點可以一步到達的結點,具體的掃瞄過程,隨儲存方式的不同而不同;

(2)一旦發現有這樣乙個結點,記為v,滿足d[v] > d[u] + w(u, v),則將d[v]的值減小,減小到和d[u] + w(u, v)相等。其中,w(u, v)為圖中的邊u-v的長度,由於u-v必相鄰,所以這個長度一定已知(不然我們得到的也不叫乙個完整的圖);這種操作叫做鬆弛。

引用內容

鬆弛操作的原理是著名的定理:「三角形兩邊之和大於第三邊」,在資訊學中我們叫它三角不等式。所謂對i,j進行鬆弛,就是判定是否d[j]>d[i]+w[i,j],如果該式成立則將d[j]減小到d[i]+w[i,j],否則不動。

(3)上一步中,我們認為我們「改進了」結點v的最短路徑,結點v的當前路徑長度d[v]相比於以前減小了一些,於是,與v相連的一些結點的路徑長度可能會相應地減小。注意,是可能,而不是一定。但即使如此,我們仍然要將v加入到佇列中等待處理,以保證這些結點的路徑值在演算法結束時被降至最優。當然,如果連線至v的邊較多,演算法執行中,結點v的路徑長度可能會多次被改進,如果我們因此而將v加入佇列多次,後續的工作無疑是冗餘的。這樣,就需要我們維護乙個bool陣列inqueue,來記錄每乙個結點是否已經在佇列中。我們僅將尚未加入佇列的點加入佇列。

演算法能否結束?

對於不存在負權迴路的圖來說,上述演算法是一定會結束的。因為演算法在反覆優化各個最短路徑長度,總有乙個時刻會進入「無法再優化」的局面,此時一旦佇列讀空,演算法就結束了。然而,如果圖中存在一條權值為負的迴路,就糟糕了,演算法會在其上反覆執行,通過「繞圈」來無休止地試圖減小某些相關點的最短路徑值。假如我們不能保證圖中沒有負權迴路,一種「結束條件」是必要的。這種結束條件是什麼呢?

思考bellman-ford演算法,它是如何結束的?顯然,最樸素的bellman-ford演算法不管迴圈過程中發生了什麼,一概要迴圈|v|-1遍才肯結束。憑直覺我們可以感到,spfa演算法「更聰明一些」,就是說我們可以猜測,假如在spfa中,乙個點進入佇列——或者說乙個點被處理——超過了|v|次,那麼就可以斷定圖中存在負權迴路了。

最短路徑本身怎麼輸出?

在一幅圖中,我們僅僅知道結點a到結點e的最短路徑長度是73,有時候意義不大。這附圖如果是地圖的模型的話,在算出最短路徑長度後,我們總要說明「怎麼走」才算真正解決了問題。如何在計算過程中記錄下來最短路徑是怎麼走的,並在最後將它輸出呢?

path陣列,path[i]表示從s到i的最短路徑中,結點i之前的結點的編號。注意,是「之前」,不是「之後」。最短路徑演算法的核心思想成為「鬆弛」,原理是三角形不等式,方法是上文已經提及的。我們只需要在借助結點u對結點v進行鬆弛的同時,標記下path[v] = u,記錄的工作就完成了。

輸出時可能會遇到一點難處,我們記的是每個點「前面的」點是什麼,輸出卻要從最前面往最後面輸,這不好辦。其實很好辦,見如下遞迴方法:

程式**

void printpath(int k)

}tmp = tmp->next;

}}while(closed < open);

}然後是鄰接矩陣的:

程式**

void spfa()

int closed = 0, open = 1;

queue[1] = s;

dist[s] = 0;do}

}while(closed < open);

}

最短路徑演算法 SPFA

求最短路徑的演算法有許多種,除了排序外,恐怕是oi界中解決同一類問題演算法最多的了。最熟悉的無疑是dijkstra,接著是bellman ford,它們都可以求出由乙個源點向其他各點的最短路徑 如果我們想要求出每一對頂點之間的最短路徑的話,還可以用floyd warshall。spfa是這篇日誌要寫...

SPFA演算法 最短路徑

只要最短路徑存在,spfa演算法必定能求出最小值,spfa對bellman ford演算法優化的關鍵之處在於意識到 只有那些在前一遍鬆弛中改變了距離估計值的點,才可能引起他們的鄰接點的距離估計值的改變。為什麼隊列為空就不改變了呢?就是因為要到下一點必須經過它的前乙個鄰接點。spfa可以處理負權邊。很...

SPFA演算法 最短路徑

spfa是一種求單源最短路的演算法 演算法中需要用到的主要變數 int n 表示n個點,從1到n標號 int s,t s為源點,t為終點 int d n d i 表示源點s到點i的最短路 int p n 記錄路徑 或者說記錄前驅 queue q 乙個佇列,用stl實現,當然可有手打佇列,無所謂 bo...