《啊哈!演算法》第6章最短路徑

OpenSoucre發表於2014-06-24

第一節 Floyd-Warshall演算法

本演算法可以求任意兩個點之間的最短路徑,又稱“多源最短路徑”,其時間複雜度為O(n^3)

其核心部分只有下面幾行,注意加法的溢位處理

    //floyd最短路徑演算法的核心部分
    for(int k = 0; k < n; ++ k){
        for(int i = 0 ; i < n ; ++ i){
            for(int j = 0 ; j < n ; ++ j){
                if(grid[i][k]!=INT_MAX && grid[k][j]!=INT_MAX &&grid[i][j] > grid[i][k]+grid[k][j]){
                    grid[i][j] = grid[i][k] + grid[k][j];
                    path[i][j] = k;
                }
            }
        }
    }

 

/*
 *基於有向圖的floyd最短路徑演算法
 *floyd演算法不能解決帶有“負權迴路”的圖
 */

#include <iostream>
#include <vector>
#include <algorithm>
#include <climits>
#include <cstdio>
using namespace std;

typedef vector<vector<int> > VVI;

void floyd(VVI& grid, VVI& path){
    int n = grid.size();
    //floyd最短路徑演算法的核心部分
    for(int k = 0; k < n; ++ k){
        for(int i = 0 ; i < n ; ++ i){
            for(int j = 0 ; j < n ; ++ j){
                if(grid[i][k]!=INT_MAX && grid[k][j]!=INT_MAX &&grid[i][j] > grid[i][k]+grid[k][j]){
                    grid[i][j] = grid[i][k] + grid[k][j];
                    path[i][j] = k;
                }
            }
        }
    }
}

void print(int x, int y, VVI& path){
   int k = path[x][y];
   //cout<<"k="<<k<<endl;
   if(k==-1) return;
   print(x,k,path);
   printf("%d->",k);
   print(k,y,path);
}

int main(){
    int n,m;
    cin >> n >> m;
    VVI grid(n,vector<int>(m,INT_MAX));
    for(int i = 0 ; i < n; ++ i) grid[i][i] = 0;
    for(int i = 0 ; i < m; ++ i){
        int a,b,e;
        cin >> a >> b >> e;
        grid[--a][--b] = e;
    }
    VVI path(n,vector<int>(m,-1));
    floyd(grid,path);
    //輸出最終的結果
    cout<<string(30,'=')<<endl;
    printf("start\tend\tlength\tpath\n");
    for(int i = 0 ; i < n ; ++ i){
        for(int j = 0 ;  j < n; ++ j){
            printf("%4d\t%4d\t%4d\t",i+1,j+1,grid[i][j]);
            printf("%d->",i+1);
            print(i,j,path);
            printf("%d\n",j+1);
        }
        //printf("\n");
    }
    cout<<string(30,'=')<<endl;
    return 0;
}
floyd最短路徑演算法

 第二節 Dijkstra演算法-通過邊實現鬆弛

/*
 *單源最短路徑演算法
 */

#include <iostream>
#include <vector>
#include <algorithm>
#include <climits>
using namespace std;

typedef vector<vector<int> > VVI;
typedef vector<bool> VB;
typedef vector<int> VI;

void printPath(VI& path, int u, int v){
    vector<int> res;
    res.push_back(v+1);
    while(path[v]!=u){
        res.push_back(path[v]+1);
        v = path[v];
    }
    res.push_back(u+1);
    reverse(res.begin(),res.end());
    cout<<"路徑:";
    for(int i = 0 ; i < res.size(); ++ i)
        cout << "->"<< res[i];
    cout<<endl;
}

int main(){
    int n,m;
    cin >> n >>m;
    VVI grid(n,vector<int>(n,INT_MAX));
    for(int i = 0 ; i < n ; ++ i) grid[i][i] = 0;
    for(int i = 0 ; i < m; ++ i){
        int a,b,e;
        cin >> a >> b >> e;
        --a;--b;
        grid[a][b] = e;
    }

    VB visit(n,false);
    visit[0] = true;

    VI dist(grid[0].begin(),grid[0].end());
    VI path(n,0);
    for(int i = 0 ; i < n ; ++ i) {
        if(dist[i] == INT_MAX) path[i] = -1;
        else path[i] = 0;
    }
    //Dijkstra演算法核心語句
    for(int i = 0 ; i < n-1; ++ i){
        int minDist = INT_MAX, minIndex = i;
        for(int j = 0 ; j < n ; ++ j){
            if(!visit[j] && dist[j] < minDist){
                minDist = dist[j];
                minIndex = j;
            }
        }
        visit[minIndex] = true;
        for(int p = 0; p < n;++ p){
            if(grid[minIndex][p] < INT_MAX ){
                if(dist[p] > dist[minIndex]+grid[minIndex][p]){
                    dist[p] = dist[minIndex]  + grid[minIndex][p];
                    path[p] = minIndex;
                }
            }
        }
    }

    printPath(path,0,n-1);

    //輸出最終的結果
    cout<<"距離:";
    for(int i = 0 ; i < n; ++ i)
        cout<<"->"<<dist[i];
    cout<<endl;

    return 0;
}
Dijkstra最短路徑演算法

 第三節 Bellman-Ford-解決負權邊

Dijkstra演算法不能解決帶有負權邊的圖,而Bellman-Ford能計算

注意鬆弛操作只需要進行n-1輪即可,因為在一個含有n個頂點的圖中,任意兩點之間的最短路徑最多包含n-1邊

#include <iostream>
#include <vector>
#include <algorithm>

#define INF 100000

using namespace std;

struct edge{
    int u;
    int v;
    int w;
    edge(int uu = 0, int vv = 0 , int ww = 0):u(uu),v(vv),w(ww){}
};

int main(){
    int n,m;
    cin >> n >> m;
    vector<edge> e(m);
    for(int i = 0 ; i < m; ++ i){
        cin >>e[i].u >> e[i].v >> e[i].w;
        --e[i].u;--e[i].v;
    }
    vector<int> dist(n,INF),bak(n,INF);
    dist[0] = 0;
    for(int i = 0; i <n-1; ++ i){
        for(int j = 0; j < n; ++ j) bak[j] = dist[j];
        for(int j = 0; j < m; ++ j){
            if(dist[e[j].v] > dist[e[j].u] + e[j].w){
                dist[e[j].v] = dist[e[j].u] + e[j].w;
            }
        }

        //檢測陣列是否有更新,如果沒有更新,提前退出迴圈
        bool check = false;
        for(int j = 0; j < n ; ++ j) 
            if(bak[j]!=dist[j]){check=true;break;}
        if(!check) break;
    }

    //檢測負權迴路
    bool flag = false;
    for(int i = 0 ; i < m ; ++ i){
        if(dist[e[i].v] > dist[e[i].u] + e[i].w){
            flag = true;
            break;
        }
    }
    if(flag) cout<<"此圖含有負權迴路"<<endl;

    for(int i = 0 ; i < n; ++ i){
        cout<<dist[i]<<" ";
    }
    cout<<endl;

}
Bellman-Ford演算法

相關文章