【algo&ds】7.最短路徑問題

  • 單源最短路徑問題:從某固定源點出發,求其到所有其他頂點的最短路徑
    • (有向)無權圖:BFS
    • (有向)有權圖:Dijkstra算法
  • 多源最短路徑問題:求任意兩頂點間的最短路徑
    • 直接將單源最短路算法調用|V|遍
    • Floyd算法

1.BFS算法求解單源無權圖最短路徑

1.1算法描述

Snipaste_2019-11-23_09-53-57.png

廣度優先搜索,開一個額外的數組存儲每一個結點的訪問狀態,一層一層(取出隊首元素,遍歷所有相鄰且未被訪問的結點)的入隊列,然後層數++

Snipaste_2019-11-23_10-02-44.png

Snipaste_2019-11-23_10-05-46.png

這裏的額外數組就是dist[w],指的是從源點到頂點w的最短路徑長度,初始化爲-1,判斷未訪問即==-1,如果未訪問且存在邊G[v][w]則dist[w] = dist[v] +1 ;

path數組用於保存每一個頂點w的前驅頂點v,也即這條最短路徑(s->w)必定是從(s->....->v->w),通過棧來逆序輸出path[w] 、path[path[w]]....

更加詳細的算法示例可以參考視頻

1.2代碼實現

#include<iostream>
#include<stdlib.h>
#include<cstdlib>
#include<queue>
#include<stack>
#define Init -1
#define MaxVertex  100
int path[MaxVertex];  // 存儲路徑,如果當前頂點v出隊列,且存在頂點v->w的路徑,則path[w] = v
int dist[MaxVertex];  // 存儲路徑長度,即從源頂點s到當前頂點w的最短路徑dist[w]
int G[MaxVertex][MaxVertex]; // 圖,採用鄰接矩陣表示
int Ne;  // 頂點數 
int Nv;  // 邊 
typedef int Vertex;
using namespace std;


void build(){
    int v1,v2;
    // 初始化點 
    cin>>Nv;
    for(int i=1;i<=Nv;i++)
        for(int j=1;j<=Nv;j++)
            G[i][j] = 0;
    // 初始化路徑
    for(int i=1;i<=Nv;i++)
        path[i] = Init;
    // 初始化路徑長度
    for(int i=1;i<=Nv;i++)
         dist[i] = Init;
    // 初始化邊 
    cin>>Ne;
    for(int i=0;i<Ne;i++){
        cin>>v1>>v2;
        G[v1][v2] = 1; // 有向圖! 
    }
}

void Unweighted(Vertex v){
    queue<Vertex> q;
    dist[v] = 0;  // 將自己的距離置 0 ,路徑path[v]不變
    Vertex w;
    q.push(v);
    while(!q.empty()){
         w = q.front();
         q.pop();
         for(int i=1;i<=Nv;i++)
            // 如果沒被訪問過,且連通 
            if(dist[i]==Init && G[w][i]){
                dist[i] = dist[w]+1;  // 是上一步的距離 + 1 
                path[i] = w;  // w 是上一步要走路徑的下一步路徑 
                q.push(i);
            }
    }
}

// 獲取路徑 
void getTail(Vertex v){
    for(int i=1;i<=Nv;i++){
        if(i==v)
            continue;
        stack<Vertex> s;
        cout<<v<<"到"<<i<<"的最短距離是:"<<dist[i];
        Vertex w = i;
        // 當沒到達起始起點前一直做循環 
        while(path[w]!=Init){
            s.push(w);  // 入棧 
            w = path[w];
        }
        // 逆序輸出入棧元素,得到路徑 
        cout<<"    其路徑爲:";
        if(v != i)
            cout<<v;
        while(!s.empty()){
            // 輸出棧頂元素 
            cout<<"→"<<s.top();
            s.pop(); // 出棧 
        }
        cout<<endl;
    }
}


int main(){
    build();
    Unweighted(3);
    getTail(3); 
    return 0;
}

2.Dijkstra算法求解單源有權圖最短路徑

Dijkstra算法圖解.png

2.1算法描述

有權圖的單源最短路算法可以使用Dijkstra算法實現,Dijkstra算法的基本思想是對圖G(V,E)設置集合S,存放已被訪問的頂點,然後每次從集合V-S中選擇與起點s的最短距離最小的一個頂點(記爲u),訪問並加入集合S。之後,令頂點u爲中間點,優化所有起點s通過點u能夠到達的鄰接點v之間的最短路徑。這樣的操作執行n次,直到集合S已包含所有頂點。

算法的僞碼描述如下:

void Dijkstra( Vertex s ) {
    while (1) {
        V = 未收錄頂點中dist最小者;
        if ( 這樣的V不存在)
            break;
        collected[V] = true;
        for ( V 的每個鄰接點W )
            if ( collected[W] == false )
                if ( dist[V]+E<V,W> < dist[W] ) {
                    dist[W] = dist[V] + E<V,W> ;
                    path[W] = V;
                }
    }
} /* 不能解決有負邊的情況*/

引出了兩個問題:

  • 如何確定未收錄頂點中dist最小者?
  • 如何初始化dist[i]?

如何確定未收錄頂點中dist最小者?

1.直接掃描所有未收錄頂點,時間複雜度爲– O( |V| ),總的時間複雜度爲T = O( |V|^2 + |E| )對於稠密圖效果好

2.將dist存在最小堆中,時間複雜度爲– O( log|V| ),總的時間複雜度爲T = O( |V| log|V| + |E| log|V| ) = O( |E| log|V| ),對於稀疏圖效果好。

如何初始化dist[i]?

  • 對於dist[0],也就是源點可以直接初始化爲0
  • 對於存在邊G[0][w],則dist[w]可以直接初始化爲頂點s到頂點w的邊權
  • 其它的頂點w,初始化爲infinity(無窮大)

Snipaste_2019-11-23_15-56-54.png

  • 初始狀態,兩個數組的初始化如上圖所示

對於算法的詳細示例可以參考視頻

2.2代碼實現

#include<iostream>
#include<stdlib.h>
#define Inf 1000000
#define Init -1
#define MaxVertex 100
typedef int Vertex;
int G[MaxVertex][MaxVertex];
int dist[MaxVertex];  // 距離 
int path[MaxVertex];  // 路徑 
int collected[MaxVertex];  // 被收錄集合 
int Nv;   // 頂點 
int Ne;   // 邊 
using namespace std;

// 初始化圖信息 
void build(){
    Vertex v1,v2;
    int w;
    cin>>Nv;
    // 初始化圖 
    for(int i=1;i<=Nv;i++)
        for(int j=1;j<=Nv;j++)
            G[i][j] = 0;
    // 初始化路徑 
    for(int i=1;i<=Nv;i++)
        path[i] = Init;
    // 初始化距離
    for(int i=0;i<=Nv;i++)
        dist[i] = Inf;
    // 初始化收錄情況 
    for(int i=1;i<=Nv;i++)
        collected[i] = false;
    cin>>Ne;
    // 初始化點
    for(int i=0;i<Ne;i++){
        cin>>v1>>v2>>w;
        G[v1][v2] = w;  // 有向圖 
    }
}

// 初始化距離和路徑信息 
void crate(Vertex s){
    dist[s] = 0;
    collected[s] = true;
    for(int i=1;i<=Nv;i++)
        if(G[s][i]){
            dist[i] = G[s][i];
            path[i] = s;
        }
}

// 查找未收錄頂點中dist最小者
Vertex FindMin(Vertex s){
    int min = 0;  // 之前特地把 dist[0] 初始化爲正無窮 
    for(Vertex i=1;i<=Nv;i++)
        if(i != s && dist[i] < dist[min] && !collected[i])
            min = i;
    return min;
}


void Dijkstra(Vertex s){
    crate(s); 
    while(true){
        Vertex V = FindMin(s);   // 找到 
        if(!V)
            break;
        collected[V] = true;  //收錄
        for(Vertex W=1;W<=Nv;W++)
            if(!collected[W] && G[V][W]){  // 如果未被收錄
                if(dist[V] + G[V][W] < dist[W]){
                    dist[W] = G[V][W] + dist[V];
                    path[W] = V;
                }
            }
    }
}

void output(){
    for(int i=1;i<=Nv;i++)
        cout<<dist[i]<<" ";
    cout<<endl;
    for(int i=1;i<=Nv;i++)
        cout<<path[i]<<" ";
    cout<<endl;
}


int main(){
    build();
    Dijkstra(1);
    output();
    return 0;
}

3.Floyd算法求解多源最短路徑算法

#include<iostream>
#include<stdlib.h>
#define INF 1000000
#define MaxVertex 100
typedef int Vertex;
int G[MaxVertex][MaxVertex];
int dist[MaxVertex][MaxVertex];  // 距離 
int path[MaxVertex][MaxVertex];  // 路徑 
int Nv;   // 頂點 
int Ne;   // 邊 
using namespace std;

// 初始化圖信息 
void build(){
    Vertex v1,v2;
    int w;
    cin>>Nv;
    // 初始化圖 
    for(int i=1;i<=Nv;i++)
        for(int j=1;j<=Nv;j++)
            G[i][j] = INF;
    cin>>Ne;
    // 初始化點
    for(int i=0;i<Ne;i++){
        cin>>v1>>v2>>w;
        G[v1][v2] = w;  
        G[v2][v1] = w;
    }
}

void Floyd(){
    for(Vertex i=1;i<=Nv;i++)
        for(Vertex j=1;j<=Nv;j++){
            dist[i][j] = G[i][j];
            path[i][j] = -1;
        }
    for(Vertex k=1;k<=Nv;k++)
        for(Vertex i=1;i<=Nv;i++)
            for(Vertex j=1;j<=Nv;j++)
                if(dist[i][k] + dist[k][j] < dist[i][j]){
                    dist[i][j] = dist[i][k] + dist[k][j];
                    path[i][j] = k;
                }
} 

void output(){
    for(Vertex i=1;i<=Nv;i++){ 
        for(Vertex j=1;j<=Nv;j++)
            cout<<dist[i][j]<<" ";  
        cout<<endl;
    }
    cout<<endl;
    for(Vertex i=1;i<=Nv;i++){ 
        for(Vertex j=1;j<=Nv;j++)
            cout<<path[i][j]<<" ";  
        cout<<endl;
    }
}


int main(){
    build();
    Floyd();
    output();
    return 0;
}

更多詳細的算法描述請參考視頻

以及文章

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章