SPFA 的優化


SPFA 優化

眾所周知,SPFA 它死了

但有些時候你會嫌支持負邊的 dijkstra 麻煩,於是不得不選擇 SPFA

那么,你需要 SPFA 優化!

基礎篇

如果你是只想看代碼的小萌新,請看這里。

否則可以直接跳過這一篇。

SLF 優化

我們可以參考一下 dijkstra 的思路。

dijkstra 每次取隊列中的最小值,減少了同一節點入隊次數和更新 dis 次數,於是避免了死亡

那么我們感性理解一下,如果我們在 SPFA 中每次也取較小 dis 的點是不是也可以得到一定的優化呢?

我們可以通過雙端隊列,在節點入隊時實現這一點:把需要入隊的點的 dis 與隊首的比較一下,如果小於隊首則將節點從隊首入隊,否則從隊尾入隊。

LLL 優化

其實本質原理與 SLF 一樣,只是實現方式不同啦 ~

同樣使用雙端隊列,我們額外維護一下隊列中所有點的 dis 的平均值,把即將入隊的點與這個平均值比較,決定從隊首還是隊尾入隊。

貼(偽)代碼

顯而易見,這兩個優化並不相矛盾,所以我們可以同時使用!

int dis[N];
//起點是 S
inline void SPFA(int S)
{
    //下面這三個東西只在該函數中使用,於是把它們放入函數里,避免被外界調用,不易重名、出錯
	static deque<int> q;//雙端隊列
	static bool inque[N];//記錄每個點是否在隊列中
	static int sum = 0;//因為平均數不方便維護,我們選擇維護隊列中 dis 的和
	//TODO: 此處應初始化 dis 數組為 inf
	dis[S] = 0;
	q.push_back(S);
	while (q.size()) {
		int x = q.front();
		q.pop_front();
		inque[x] = 0, sum -= dis[x];//出隊維護qaq
		for (/*TODO: 遍歷 x 的所有出邊: 出邊 e 和 對應點 y  */) {
			if (dis[y] > dis[x] + w[e]) {
				dis[y] = dis[x] + w[e];
				if (!inque[y]) {
//此處為兩種優化的核心
                    //一定要注意隊列為空的特殊情況
					if (q.empty() || dis[y] < dis[q.front()] || dis[y]*q.size() < sum)
						q.push_front(y);
					else q.push_back(y);
//
					inque[y] = 1, sum += dis[y];//入隊記得維護qwq
				}
			}
		}
	}
}

十分簡單好寫呢,為什么不隨手寫一個呢

提高篇

這一篇沒有代碼。

看這里的話,前面的東西是非必須的。

SLF 優化

每次入隊時,與隊首比較,若大於隊首則插入隊尾,反之插入隊首。

HACK:鏈套菊花圖

LLL 優化

每次入隊時,與隊內平均值比較,若大於隊首則插入隊尾,反之插入隊首。

HACK:起點連一條巨大邊權的邊,使得隊內均值一直很大。

SLF 帶容錯

每次入隊時,與隊首比較,若比隊首大超過一定閾值則插入隊尾,反之插入隊首。

HACK:開大邊權,總和超過 \(10^{12}\) 時優化失效顯著。

SLF + swap

出隊時,若隊首大於隊尾,則交換首尾。

HACK:鏈套菊花圖 + 外掛誘導節點

mcfs 優化

在第 \([L,R]\) 次訪問這個節點時,將其隊首入隊,否則隊尾入隊。

HACK:網格圖表現優異,菊花圖表現差勁。

P.S. 此優化加上SLF 帶容錯 效果顯著提升

總結

所有的 SPFA 優化都是在試圖使隊列更接近優先隊列,所以 SPFA 永遠只能是一些情況下接近 dijsktra ,而在特定圖中被卡回 \(O(MN)\) 的原形。

參考:https://www.zhihu.com/question/292283275


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM