SPFA蒜法(SLF與LLL優化) C語言實現

2021-09-25 11:00:40 字數 2504 閱讀 9625

dijkstra演算法可以較快的解決單源最短路徑問題,並且spfa演算法時間複雜度更大,那我們為什麼還要用spfa呢,在有些問題中,權值是有負值的情況,但是dijkstra不能解決負權值,這時候就需要我們用spfa演算法了。

spfa本質上算是bellman-ford的優化,由於bellman-ford時間複雜度過高,我們一般更偏愛spfa,spfa可以解決負權值問題,但是無法處理負環的情況,我們可以事先通過拓撲排序判斷圖中是否存在負權迴路,所以我們先設定我們解決的是有向加權無負環圖g的最短路徑問題。

我們用乙個陣列dist記錄每個結點的最短路徑估計值,用鄰接表儲存圖,用乙個雙端佇列q來儲存等待優化的結點(為什麼用雙端佇列,方便後續的slf優化),每次優化取隊首結點u,用當前條件下u的最短路徑,對所有與u相連的結點v進行鬆弛操作,如果v被鬆弛,並且v不在佇列中,則將v入隊,直到隊列為空。

(1)初始化dist陣列為inf(無窮大)

(2)源點入隊,遍歷隊首頂點p可以拓展的邊,若隊首的邊可以鬆弛與隊首頂點p相連的頂點v,所謂鬆弛即當滿足dist[v] > dist[u] + 時,更新dist[v] = dist[u] +。更新dist[v]。

(3)若v不在佇列中,則將v入隊,否則繼續遍歷與隊首相連的下乙個頂點。

(4)迴圈(2),(3)直到隊列為空,鬆弛遍歷圖中所有的頂點。

spfa時間複雜度相較於bellman-ford已經優化了很多,但是有的時候還是太慢,不能滿足我們的需求,這時候,我們還有兩種優化spfa的方式,如下:

採用雙端佇列,對於將要加入佇列的頂點p,判斷如果dist[p]小於隊首頂點u的dist[u],則將其插入到對頭,否咋將其插入到隊尾。(這樣可以保證每一次佇列的隊首頂點u的dist都是最小的,權值小意味著可以鬆弛更多的結點,所以能達到優化的目的)

對於每個要出隊的隊首頂點u,比較dist[u]和佇列中點的dist的平均值,當dist[u]大於平均值時,將其彈出放到隊尾,迴圈取隊首頂點的操作,直到隊首頂點的dist值小於平均值的時候為止。(同slf一樣,都是盡可能的鬆弛更多的結點,以優化spfa的速度)

事實上,這兩種優化是互不干擾的,我們可以同時採取這兩種優化,網上的資料顯示,slf可提公升10%—20%,而slf+lll可以提公升近50%。但是通常spfa的時間效率不大穩定,如果不是負權值,還是採用dijkstra更方便一些。

typedef struct anode arcnode;             //邊結點的型別

typedef struct vnode vnode; //頭節點型別

typedef struct adjgraph; //鄰接表

int spfa(adjgraph g, int u, int v) ;  //dis陣列記錄路徑長度, vis陣列標記遍歷過的點

int pre[max], sum, num; //pre陣列記錄前驅結點,用於輸出最短路徑

int path_spfa[2][max]; //path_spfa[0]儲存u -> v倒序最短路徑,path_spfa[1]存正序

memset(pre, -1, sizeof pre);

memset(dis, 0x3f, sizeof dis);

dis[u] = 0, vis[u] = 1;

push_rear(&q, u);

num = 1, sum = dis[u];

while (q.size != 0)

elemtype s = pop_front(&q);

vis[t] = 0;

num--; sum -= dis[t];

p = g.adjlist[t].firstarc;

while (p != null)

else

push_rear(&q, vex);

num++; sum += dis[vex];}}

p = p->nextarc;

} } int top = -1; //將pre中存放的前驅結點倒序存入路徑輸出path[1];

int parent = v;

while (parent != -1)

cntspfa = top + 1; //cntspfa記錄陣列長度

for (int i = 0; i < cntspfa; i++)

path_spfa[1][i] = path_spfa[0][cntspfa - i - 1];

return dis[v]; //返回u -> v 的最短路徑長度

}

ps:**中inf表示無窮大,max表示陣列長度,讀者可自行設定,筆者的spfa只返回了最短路徑長度,若想得到最短路徑,可返回path_spfa[1]的首位址。**中使用的push_rear,push_rear,pop_front,get_front等函式均為雙端佇列的基本操作。

雙端佇列的實現

SPFA的兩種優化SLF和LLL

記錄乙個菜逼的成長。記下spfa的兩種優化,大牛請無視 spfa演算法有兩個優化演算法 slf 和 lll slf small label first 策略,設要加入的節點是j jj,隊首元素為i ii,若dis t j st i dist j dist i dist j st i 則將j插入隊首,...

SPFA的兩種優化SLF和LLL

spfa有兩種優化 spfa演算法有兩個優化策略slf和lll slf small label first 策略,設要加入的節點是j,隊首元素為i,若dist j x則將i插入到隊尾,查詢下一元素,直到找到某一i使得dist i x,則將i出隊進行鬆弛操作。slf 可使速度提高 15 20 slf ...

約束優化方法之拉格朗日乘子法與KKT條件

引言 本篇文章將詳解帶有約束條件的最優化問題,約束條件分為等式約束與不等式約束,對於等式約束的優化問題,可以直接應用拉格朗日乘子法去求取最優值 對於含有不等式約束的優化問題,可以轉化為在滿足 kkt 約束條件下應用拉格朗日乘子法求解。拉格朗日求得的並不一定是最優解,只有在凸優化的情況下,才能保證得到...