1. 程式人生 > >圖論-最短路問題

圖論-最短路問題

最短路問題總結

floyed演算法

可以求任意兩點的最短路,適合負邊權,也可以用於檢測任意兩點是否連通。演算法效率O(N^3)

核心程式碼:

    //d[i][j]表示節點i到j的最短路
    forint k=1;k<=n;k++)
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                d[i][j] = min(d[i][j] , d[i][k]+d[k][j]);
其中k一定是在最外面,理解為什麼在最外面是最難,這裡利用了動態規劃的思想。
理解時可以擴大維度
f[k][i][j]表示i->j經過k的最短路,f[k-1][i][j]表示經過k-1,的最短路
f[k][i][j] = min{f[k-1][i][k],f[k-1][i][k] + f[k-1][k][j]}
f[k][i][j] 由公式是有f[k-1][i][j] 轉化而來,所以k是階段。

dijkstra演算法(迪傑斯特拉)

貪心思想, 效率是O(N^2),無法處理負邊權,資料用鄰接矩陣,用優先佇列優化用鄰接表。
用dist[i]表示i到起點的距離,vist[i]表示i是否被訪問過。
策略是,訪問過的節點構成一個集合,每次更新一個點,這個點是剩下的點中到起點最短的點,
將他加入集合,然後由新加的點出發更新剩下的點。因為這裡的更新剩下的點所以無法處理負邊權
void dijkstra(int start) {
    for(int i=1;i<=n;i++){
        int x;
        int mi=INF;  // 在所有未標號的結點中,選擇一個d值最小的點x。
//這裡有點貪心的意味,每次找最小值進行走下一步。所以可以用堆來優化查詢最小值 for(int j=1;j<=n;j++) if(vist[j] == 0 && dist[j] < mi ) //如果j沒訪問,並且圓點到這個點的距離小於最小值 {x = j ; mi = dist[x];} vist[x] = 1;//這個點標記訪問 for(int j=1;j<=n;j++)//從x出發到其他點,更新其他點 if
(dist[x] + g[x][j] < dist[j]) //源點經過x到j 的距離小於其他路到j 的距離 { dist[j]=dist[x] + g[x][j];//更新 path[j]=x;//j經過x } } }

bellman-ford演算法

鬆弛原理,效率O(NV),可以處理負邊權,也可以判斷是否用負權迴路。採取邊目錄儲存
bellman-ford的理論:
對於不存在負權迴路的圖,意味著,最短路最多(不算起點)只經過n-1個節點。
那麼就可以通過n-1次鬆弛操作就可以得到最短路
//採取邊目錄的儲存方式,E[i]表示第i條邊
void b_ford(int start){
    dist[start]=0;  
    for(int i=1;i<n;i++){
        for(int j=1;j<=m;j++){//m是邊數 
            if(dist[E[j].x] + w[j] < dist[E[j].y])
            //dist[E[j].x]表示邊j的第一個點到起點的長度
            {
                 dist[E[j].y]= dist[E[j].x] + w[j];  
            }
            if(dist[E[j].y] + w[j] < dist[E[j].x])
            //dist[E[j].y]表示邊j的第2 個點到起點的長度
                 dist[E[j].x]= dist[E[j].y] + w[j];   
            //這道題是無向圖,所以每個邊要判斷兩次,對於有 向圖則是一次 
            //write(n);
        }       
    }
    //這裡可以在對每個邊判斷是否還能不能鬆弛,如果還能鬆弛,說明有負權迴路
}

SPFA!

SPFA是使用佇列實現的Bellman-Ford演算法。鄰接表儲存()
每次從源點對連線他所有邊(u,v)進行鬆弛,如果能夠鬆弛,說明v可以修改,則將v壓入鬆弛佇列
操作步驟:

1. 初始佇列和標記陣列
2. 源點入隊。
3. 對隊首點出發的所有邊進行鬆弛操作(即更新最小值)。
4. 將不在佇列中的尾結點入隊。
5. 隊首點更新完其所有的邊後出隊。

queue<int> q;
bool inqueue[N];                   // 是否在佇列中
int cnt[N];                        // 檢查負環時使用:結點進隊次數。如果超過n說明有負環。
bool SPFA(int start) {        // 有負環則返回false
    // 初始佇列和標記陣列
    for (int i=0; i<n; i++)  d[i]=INF;
    d[start]=0;
    memset(cnt,0,sizeof(cnt));
    q.push(start);                            // 源點入隊
    cnt[start]++;
    while (!q.empty()) {
        int x=q.front(); q.pop();
        inqueue[x]=false;
        // 對隊首點出發的所有邊進行鬆弛操作(即更新最小值)
        for (edge *e=adj[x];e!=NULL;e=e->next){
            int &v=e->v, &w=e->w;
            if (d[v]>d[x]+w) {
               d[v] = d[x]+w;
               // 將不在佇列中的尾結點入隊
               if (!inqueue[v]) {
                  inqueue[v]=true;
                  q.push(v);
                  if (++cnt[v]>n) return false;          // 有負環
               } 
            }
        }
   }
   return true;
}