演算法簡易過程:
迪傑斯特拉演算法(樸素) 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;
}
測試如下: