圖論 *最短路*
多源最短路:Floyd
所謂多源,就是求圖中任意兩點的最短路。
floyd是一種動態規劃的做法。
首先我們給出狀態定義:$f(i,j,k)$ 表示除了點$i j$外,只經過$1~k$個點, $i$到$j$的最短路,不難得出狀態轉移方程:$ f(i,j,k) = min(f(i,k,k-1)+f(k,j,k-1)) $
優化掉$k$那一維之後就變成 : $f(i,j) = min(f(i,k) + f(k,j))$ 。該演算法處理圖的邊權,可正可負,複雜度 $O(n^3)$。
for(int k=1;k<=n;++k){ for(int i=1;i<=n;++i)for(int j=1;j<=n;++j) f[i][j] = min(f[i][j],f[i][k] + f[k][j]); //初始化f[i][j]即為兩點邊權,否則為正無窮
單源最短路
就是求從給定點到其餘點的最短路
Dijkstra
該演算法需要一個$dis$陣列,$dis[u]$代表從點$u$到起點的最短路。
初始時,起點dis值為0,其餘都為正無窮。
每次取出dis最小的點並刪除,遍歷他的所有出邊進行鬆弛操作,直至全部結束。
取dis的過程可以用堆,也就是STL裡的priority_queue來實現,總複雜度$O(nlogn)$ 。
鬆弛操作:對於一個點v和一個點u,若dis[v] > dis[u] + w[u][v] 則dis[v]更新為dis[u] + w[u][v] ,可以形象的想象一個圖,原來的dis中點v從原點x過來經過一條路徑,現在變為從x到u再到v,路徑更短,但看起來像繩子更鬆的樣子,這也是為什麼叫作鬆弛操作。
PS:Dijkstra只能用在非負權圖,負權圖得SPFA!!!
struct qwq{ int u,dis; bool operator <(qwq a)const{return dis > a,dis;}//STL預設大根堆,這樣改成小根堆,也就是先出小的 }; inline void dijkstra(){ memset(dis,0x3f3f3f3f,sizeof(dis)); priority_queue<qwq> q; dis[start] = 0; q.push((qwq){start,0}); while(!q.empty()){ int u.q.top();q.pop(); if(vis[u])continue; vis[u] = 1; for(int i=head[u];i;i=nxt[i]){ int v = to[i]; if(dis[v] > dis[u] + w[i]){ dis[v] = dis[u] + w[i]; q.push((qwq){dis[v],v}); } } } }
SPFA
首先關於這個演算法有一句話不得不說——————————它 !死 !了 !
但是呢,又沒完全死,因為在構造資料下它太容易被卡掉,但是有的時候我們寫不出正解,所以它反而又是我們不得不用的。
首先他的複雜度,點數為N,邊數為M, 期望複雜度$O(M) $ 但最壞複雜度$O(NM)$ 所以能用dijkstra 就別用SPFA 反覆鞭屍
我們同樣維護一個$dis$ ,只不過這回用佇列來實現。
首先放入起點
每次取出隊首元素,訪問所有出邊,進行鬆弛操作,如果連線的點沒有入過隊,將它入隊。
直到隊空
inline void spfa(){ memset(dis,0x3f3f3f3f,sizeof(dis)); dis[start] = 0; vis[start] = 1; queue<int>q; q.push(start); while(!q.empty()){ int u = q.front();q.pop(); for(int i=head[u];i;i=nxt[i]){ int v = to[i]; if(dis[v] > dis[u] + w[i]){ dis[v] = dis[u] + w[i]; if(!vis[v])q.push(v),vis[v] = 1; } } } }