1. 程式人生 > 其它 >圖論 *最短路*

圖論 *最短路*

多源最短路: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;
            }
        }
    }
}