ACM:最短路,dijkstra,鄰接表的建立,使用鄰接表跟優先隊列的dijkstra,Bellman-Ford,Floyd。。

來源:互聯網
上載者:User

標籤:style   blog   color   使用   2014   os   

(一)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值最小的節點xif(!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小時返回truereturn 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值最小的節點xmemset(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;}


















聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.