dijkstra迪傑斯特拉演算法(鄰接表法)

GiperHsiue發表於2024-05-17


演算法簡易過程:

迪傑斯特拉演算法(樸素) O(n^2)

G={V,E} V:點集合 E:邊集合

初始化時 令 S={某源點ear}, T=V-S= {其餘頂點},T中頂點對應的距離(ear, Vi)值

      若存在,d(ear,Vi)為弧上的權值, dist【i】

      若不存在,d(ear,Vi)為 無窮大, dist【i】

迴圈 n - 1次(n個點):

1、從T中選取一個與S中頂點 有關聯邊 且 權值最小 的頂點 pos,加入到 S中

  (這裡使用 flag陣列來確定 是否屬於 S集合,true為屬於)

  (等於是 每次 選取 T點集中 dist最小的頂點 作為 pos 加入 S,既 flag置為 true)

2、對其餘T中頂點Vi的距離值進行修改:若加進 pos 作中間頂點,從ear -> pos -> Vi 的距離值縮短,則 更新dist

  (等於是 找出所有 pos -> Vi 邊(有邊連線的), 再加上原來源ear -> pos 權重,對比dist陣列,如果權重更小則更新 => 更新dist最短路徑長度,更新prev陣列 更新前驅頂點為pos)

求單源有向圖最短路徑

使用鄰接表法來儲存頂點和邊,錄入有向圖。

(當然也可以無向圖,不過錄入時要錄入兩次,比如 a b 3 b a 3)

程式碼如下:

//
// Created by Giperx on 2022/11/27.
//
#include <iostream>
#include <vector>
#include <stack>
using namespace std;
#define INFINE 99999 // 定義最大
// 鄰接表
struct ArcNode // 邊資訊
{
    int adjvex;//有向邊的 目標頂點 下標(從1開始)
    int weight;//邊的權值
    struct ArcNode *next; //鄰接表,指向下一個鄰接邊資訊
};

struct VertexNode // 頂點
{
    int vertex;//頂點下標(1 ~)
    ArcNode *firstedge;// 有向邊資訊節點指標(源為vertex)
};

struct AdjList // 圖
{
    vector<VertexNode> adjlist;//頂點陣列
    int vexnum;  //頂點數 
    int arcnum;  //邊數
};

// 圖的初始化
void createGraph(AdjList& G){
    cout << "輸入頂點數 邊數:" << endl;
    cin >> G.vexnum >> G.arcnum;
    // 初始化G的頂點陣列
    for(int i = 0; i <= G.vexnum; i ++){ // 下標從1開始,所以初始化vexnum + 1個頂點(0無作用)
        VertexNode* tmp = new VertexNode;
        tmp->vertex = i, tmp->firstedge = nullptr;
        G.adjlist.emplace_back(*tmp);
    }
    //邊資訊
    // n1:源頂點     n2:目標頂點   we:權重(距離)
    int n1, n2, we;
    cout << "輸入邊資訊:(a b we):" << endl; // a -> b  weight: we
    for(int i = 0; i < G.arcnum; i ++){
        cin >> n1 >> n2 >> we;
        // 初始化一個邊節點,目標頂點為n2
        ArcNode* tmp = new ArcNode;
        tmp->adjvex = n2, tmp->weight = we;
        // 頭插法 將邊資訊節點插入
        // 節約時間(尾插要一直遍歷到尾部插入)
        tmp->next = G.adjlist[n1].firstedge;
        G.adjlist[n1].firstedge = tmp;
    }
}

// 獲取兩頂點之間權重weight(距離)
int getWeight(AdjList& G, int n1, int n2){
    if(n1 == n2) return 0;

    ArcNode* tmp = G.adjlist[n1].firstedge;
    while(tmp){
        if(tmp->adjvex == n2) return tmp->weight;
        tmp = tmp->next;
    }
    // 兩點之間沒有邊,返回INFINE
    return INFINE;
}

// 迪傑斯特拉演算法(樸素)
//G={V,E}   V:點集合   E:邊集合
//初始化時 令 S={某源點ear}, T=V-S= {其餘頂點},T中頂點對應的距離(ear, Vi)值
//          若存在,d(ear,Vi)為弧上的權值, dist【i】
//          若不存在,d(ear,Vi)為 無窮大, dist【i】
// 迴圈 n - 1次(n個點):
//  從T中選取一個與S中頂點 有關聯邊 且 權值最小 的頂點 pos,加入到 S中
//      (這裡使用 flag陣列來確定 是否屬於 S集合,true為屬於)
//      (等於是 每次 選取 T點集中 dist最小的頂點 作為 pos 加入 S,既 flag置為 true)
//  對其餘T中頂點Vi的距離值進行修改:若加進 pos 作中間頂點,從ear -> pos -> Vi 的距離值縮短,則 更新dist
//      (等於是 找出所有 pos -> Vi 邊(有邊連線的), 再加上原來源ear -> pos 權重,
//      對比dist陣列,如果權重更小則更新 => 更新dist最短路徑長度,更新prev陣列 更新前驅頂點為pos)
void Dijkstra(AdjList& G, int ear, vector<int>& prev, vector<int>& dist){
    // 初始化
    // flag陣列記錄 某點是否納入已找到點集合
    // prev陣列記錄 前驅頂點下標
    // dist陣列記錄 從源頂點ear 到 i頂點的最短路徑
    vector<bool> flag (G.adjlist.size() + 1, false);
    for(int i = 1; i <= G.vexnum; i ++) dist[i] = getWeight(G, ear, i), prev[i] = ear;
    flag[ear] = true, prev[ear] = 0;
    // 開始
    for(int i = 2; i <= G.vexnum; i ++){
        int pos = 1; // 未納入的距離最小的頂點
        int weiMin = INFINE;
        for(int j = 1; j <= G.vexnum; j ++){
            if(!flag[j] && dist[j] < weiMin){
                weiMin = dist[j], pos = j;
            }
        }

        flag[pos] = true;
        for(int j = 1; j <= G.vexnum; j ++){
            if(!flag[j]){ // 未納入點集中,找到pos到這些點的距離,與dist陣列比較是否更新
                int tmpWei = getWeight(G, pos, j);
                if(tmpWei != INFINE) tmpWei = tmpWei + weiMin; // 兩點距離應該為ear -> pos -> j
                if(tmpWei < dist[j]) {
                    dist[j] = tmpWei; // 距離更小則更新dist
                    prev[j] = pos; // 前頂點更新為pos
                }
            }
        }
    }

}

// 找路徑
void pathDist(vector<int>& prev, vector<int>& dist, int ear){
    // prev陣列中為1有2種情況(djikstra初始化過程的時候全賦值為1,後續一直未改變):
    // 1:從ear到 頂點 只有 ear -> 頂點 這一條路最短
    // 2:無法從ear到達的頂點
    for(int i = 1; i <= prev.size() - 1; i ++){
        stack<int> trace;
        if(ear == i) continue;
        cout << ear << " 到 " << i ;
        // 無連通
        if(dist[i] == INFINE) {
            cout << "無連通" << endl;
            continue;
        }
        cout << "最短距離:" << dist[i] << "  最短路徑:";
        int tmp = i;
        while(tmp){ //  源頂點prev是0
            trace.push(tmp);
            tmp = prev[tmp];
        }
        // 開始出棧, 棧頂一定是ear源頂點
        cout << trace.top();
        trace.pop();
        while(!trace.empty()){
            cout << " -> " << trace.top();
            trace.pop();
        }
        cout << endl;
    }
}
int main(){
    AdjList G;
    createGraph(G);
    // prev陣列記錄 前驅頂點下標
    vector<int> prev (G.vexnum + 1, 0);
    // dist陣列記錄 從源頂點ear 到 i頂點的最短路徑
    vector<int> dist (G.vexnum + 1, INFINE);
    // 從源點ear 出發,到達其餘所有點的最短路徑
    cout << "輸入源頂點ear:";
    int ear;
    cin >> ear;
    Dijkstra(G, ear,prev, dist);
    pathDist(prev, dist, ear);
//    for(int &x:prev) cout << x << ' ';
//    for(int &x:dist) cout << x << ' ';
    return 0;
}


測試如下:



相關文章