ACM:最短路,dijkstra,鄰接表的建立,使用鄰接表跟優先佇列的dijkstra,Bellman-Ford,Floyd。。
(一)dijkstra,鄰接矩陣
所有邊權均為正,不管有沒有環,求單個源點出發,到所有節點的最短路。該方法同時適用於有向圖和無向圖。
#include <iostream> #include <string> #include <stack> using namespace std; const int MAXN = 1000; const int INF = 100000000; int n, m; int maze[MAXN][MAXN], vis[MAXN], d[MAXN], fa[MAXN]; //d[i]表示節點i到源點0的距離 stack<int> s; void print_path1(int j) { if(j == 0) return ; s.push(j); while(j) { for(int i = 0; i < n; ++i) { if(d[j] == d[i] + maze[i][j]) { s.push(i); j = i; break; } } } cout << s.top(); s.pop(); while(!s.empty()) { cout << "->" << s.top(); s.pop(); } cout << endl; } void print_path2(int j) { if(j == 0) return ; while(j) { s.push(j); j = fa[j]; } s.push(j); cout << s.top(); s.pop(); while(!s.empty()) { cout << "->" << s.top(); s.pop(); } cout << endl; } int main() { freopen("E://data.txt", "r", stdin); cin >> n >> m; for(int i = 0; i < n; ++i) { for(int j = 0; j < n; ++j) { maze[i][j] = INF; } } for(int i = 0; i < m; ++i) { int u, v, w; cin >> u >> v >> w; maze[u][v] = maze[v][u] = w; } memset(vis, 0, sizeof(vis)); for(int i = 0; i < n; ++i) d[i] = (i == 0 ? 0 : INF); //初始化d陣列 for(int i = 0; i < n; ++i) { //迴圈n次 int m = INF, x; for(int y = 0; y < n; ++y) { //在所有未標號的節點中,選出d值最小的節點x if(!vis[y] && d[y] <= m) m = d[x=y]; } vis[x] = 1; for(int y = 0; y < n; ++y) { //對於從x出發的所有邊(x, y),更新d[y] = min(d[y], d[x]+maze[x][y]) if(d[y] > d[x] + maze[x][y]) { d[y] = d[x] + maze[x][y]; fa[y] = x; //維護父親指標 } } } for(int i = 0; i < n; ++i) { cout << d[i] << endl; print_path1(i); //列印路徑方法1:從終點出發,不斷順著d[j] == d[i] + maze[i][j]的邊(i, j)從節點j退回到節點i,直到回到起點。 print_path2(i); //列印路徑方法2:空間換時間!在更新d陣列的時候維護父親指標! } return 0; }
(二)鄰接表的建立
鄰接表既可以用於有向圖也可以用於無向圖,在這種表示方法中,每個節點i都有一個連結串列,裡面儲存著從i出發的所有邊,對於無向圖來說,每條邊會在鄰接表中出現兩次。
我們這裡用陣列實現連結串列:首先給每條邊編號,然後用first[u]儲存節點u的第一條邊的編號,next[e]表示編號為e的邊的“下一條邊”的編號。
下面的程式碼針對有向圖,建立鄰接表。
上述程式碼的巧妙之處是插入到連結串列的首部而非尾部,這樣就避免了對連結串列的遍歷。在這裡,同一個起點的各條邊在鄰接表中的順序和讀入順序正好相反。#include <iostream> using namespace std; const int MAXN = 1000; int first[MAXN], next[MAXN], u[MAXN], v[MAXN], w[MAXN]; int main() { int n, m; cin >> n >> m; for(int i = 0; i < n; ++i) first[i] = -1; for(int e = 0; e < m; ++e) { cin >> u[e] >> v[e] >> w[e]; next[e] = first[u[e]]; first[u[e]] = e; } for(int i = 0; i < n; ++i) cout << first[i] << endl; for(int e = 0; e < m; ++e) cout << next[e] << endl; return 0; }
(三)使用鄰接表跟優先佇列的dijkstra。
queue跟priority_queue的唯一區別是,在優先佇列中,元素並不是按照進入佇列的先後順序排列,而是按照優先順序的高低順序排列。pop()刪除的是優先順序最高的元素,而不一定是最先進入佇列的元素。所以,獲取對首元素的方法不再是front(),而是top()。
struct cmp{ bool operator() (const int a, const int b) { //a的優先順序比b小時返回true return a % 10 > b % 10; } }; priority_queue<int, vector<int>, cmp> q; //“個位數大的優先順序反而小”的整數優先佇列
宣告一個小整數先出佇列的優先佇列:
priority_queue< int, vector<int>, greater<int> > q;
在dijkstra演算法中,不僅需要找出最小的d[i],要連同這個節點的編號一起從優先佇列中彈出來,所以我們用pair
為了方便起見,我們用typedef pair<int, int> pii自定義一個pii型別,則priority_queue< pii, vector<pii>, greater<pii> > q 就定義了一個由二元組構成的優先佇列!
pair定義了它自己的排序規則——先比較第一維,相等時才比較第二維,因此需要按(d[i], i)而不是(i, d[i]) 的方式組合!
利用鄰接表+二叉堆來實現dijkstra演算法的程式碼如下:
#include <iostream>
#include <queue>
using namespace std;
const int MAXN = 1000;
const int MAXM = 100000;
const int INF = 100000000;
int n, m;
int first[MAXN], d[MAXN], done[MAXN]; //在尋找距離源點最近的點x過程中,done[i]表示第i個節點已經被處理過
int u[MAXM], v[MAXM], w[MAXM], next[MAXM];
typedef pair<int, int> pii;
int main() {
cin >> n >> m;
for(int i = 0; i < n; i++) first[i] = -1; //初始化鄰接表的表頭
for(int e = 0; e < m; ++e) { //鄰接表的建立
cin >> u[e] >> v[e] >> w[e];
next[e] = first[u[e]];
first[u[e]] = e;
}
priority_queue< pii, vector<pii>, greater<pii> > q; //用於在所有未處理過的節點中,選出d值最小的節點x
memset(done, 0, sizeof(done)); //一開始假設所有節點都沒有被處理過
q.push(make_pair(d[0], 0)); //起點進入優先佇列
for(int i = 0; i < n; ++i) d[i] = (i == 0 ? 0 : INF);
while(!q.empty()) {
pii u = q.top();
q.pop();
int x = u.second; //x表示當前d值最小的節點的節點號
if(done[x]) continue; //已經算過,忽略
done[x] = 1;
for(int e = first[x]; e != -1; e = next[e]) { //遍歷從x出發的所有邊(x,y)更新d[y]
if(d[v[e]] > d[x] + w[e]) {
d[v[e]] = d[x] + w[e]; //鬆弛成功,更新d[v[e]]
q.push(make_pair(d[v[e]], v[e]));
}
}
}
for(int i = 0; i < n; ++i) cout << d[i] << endl;
return 0;
}
(四)Bellman-Ford演算法
當圖中有負權的時候,最短路就不一定存在了,但是還是可以在最短路存在的情況下把它求出來。
如果最短路存在,則該最短路一定不含環!
原因:分為正環,零環,負環三種情況考慮!
如果是正環或零環,那最短路肯定不經過它!如果是負環,那肯定就不存在最短路了!
既然最短路不含環,那麼該最短路就最多隻經過n-1個節點(起點不算),所以可以通過n-1輪鬆弛操作得到!
#include <iostream>
using namespace std;
const int MAXN = 1000;
const int INF = 100000000;
int n, m;
int d[MAXN], u[MAXN], v[MAXN], w[MAXN];
int main() {
cin >> n >> m;
for(int e = 0; e < m; ++e) {
cin >> u[e] >> v[e] >> w[e];
}
for(int i = 0; i < n; ++i) d[i] = INF;
d[0] = 0;
for(int k = 0; k < n-1; ++k) { //迭代n-1次
for(int e = 0; e < m; ++e) { //檢查每條邊
int x = u[e];
int y = v[e];
if(d[x] < INF) d[y] = min(d[y], d[x] + w[e]); //鬆弛操作
}
}
for(int i = 0; i < n; ++i) cout << d[i] << endl;
return 0;
}
用佇列實現的話,效率會更高,像這樣:
#include<iostream>
#include<string>
#include<queue>
using namespace std;
const int INF = 1000000000;
const int MAXN = 1000;
const int MAXM = 100000;
int n, m;
int first[MAXN], d[MAXN];
int u[MAXM], v[MAXM], w[MAXM], next[MAXM];
int main() {
cin >> n >> m;
for(int i = 0; i < n; ++i) first[i] = -1;
for(int e = 0; e < m; ++e) {
cin >> u[e] >> v[e] >> w[e];
next[e] = first[u[e]];
first[u[e]] = e;
}
queue<int> q;
int inq[MAXN];
for(int i = 0; i < n; ++i) d[i] = (i==0 ? 0 : INF);
memset(inq, 0, sizeof(inq));
q.push(0);
while(!q.empty()) {
int x = q.front(); q.pop();
inq[x] = 0; //標記x不在佇列中
for(int e = first[x]; e != -1; e = next[e]) if(d[v[e]] > d[x]+w[e]) {
d[v[e]] = d[x] + w[e];
if(!inq[v[e]]) { //如果點v[e]不在佇列中
inq[v[e]] = 1; //標記點v[e]在佇列中
q.push(v[e]);
}
}
}
for(int i = 0; i < n; i++) cout << d[i] << endl;
return 0;
}
(五)Floyd演算法
如果要求計算每兩點之間的最短路:
#include <iostream>
using namespace std;
const int MAXN = 1000;
const int INF = 1000000;
int d[MAXN][MAXN];
int n;
int main() {
cin >> n;
for(int i = 0; i < n; ++i) {
for(int j = 0; j < n; ++j) {
if(i == j) d[i][j] = 0;
else d[i][j] = INF;
}
}
for(int k = 0; k < n; ++k) {
for(int i = 0; i < n; ++i) {
for(int j = 0; j < n; ++j) {
if(d[i][j] < INF && d[k][j] < INF) d[i][j] = min(d[i][j], d[i][k]+d[k][j]);
}
}
}
for(int i = 0; i < n; ++i) {
for(int j = 0; j < n; ++j) {
cout << d[i][j] << endl;
}
}
return 0;
}