求最短路的相关方法

来源:互联网 发布:重生之网络娱乐女主 编辑:程序博客网 时间:2024/06/05 02:18

原文链接

(一)dijkstra,邻接矩阵

所有边权均为正,不管有没有环,求单个源点出发,到所有节点的最短路。该方法同时适用于有向图和无向图。

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. #include <iostream>  
  2. #include <string>  
  3. #include <stack>  
  4. using namespace std;  
  5.   
  6. const int MAXN = 1000;  
  7. const int INF = 100000000;  
  8. int n, m;  
  9. int maze[MAXN][MAXN], vis[MAXN], d[MAXN], fa[MAXN];  //d[i]表示节点i到源点0的距离  
  10.   
  11. stack<int> s;  
  12. void print_path1(int j) {  
  13.     if(j == 0) return ;  
  14.     s.push(j);  
  15.     while(j) {  
  16.         for(int i = 0; i < n; ++i) {  
  17.             if(d[j] == d[i] + maze[i][j]) {  
  18.                 s.push(i);  
  19.                 j = i;  
  20.                 break;  
  21.             }  
  22.         }  
  23.     }  
  24.     cout << s.top();  
  25.     s.pop();  
  26.     while(!s.empty()) {  
  27.         cout << "->" << s.top();  
  28.         s.pop();  
  29.     }  
  30.     cout << endl;  
  31. }  
  32.   
  33. void print_path2(int j) {  
  34.     if(j == 0) return ;  
  35.     while(j) {  
  36.         s.push(j);  
  37.         j = fa[j];  
  38.     }  
  39.     s.push(j);  
  40.     cout << s.top();  
  41.     s.pop();  
  42.     while(!s.empty()) {  
  43.         cout << "->" << s.top();  
  44.         s.pop();  
  45.     }  
  46.     cout << endl;  
  47. }  
  48.   
  49. int main() {  
  50.     freopen("E://data.txt""r", stdin);  
  51.     cin >> n >> m;  
  52.     for(int i = 0; i < n; ++i) {  
  53.         for(int j = 0; j < n; ++j) {  
  54.             maze[i][j] = INF;  
  55.         }  
  56.     }  
  57.     for(int i = 0; i < m; ++i) {  
  58.         int u, v, w;  
  59.         cin >> u >> v >> w;  
  60.         maze[u][v] = maze[v][u] = w;  
  61.     }  
  62.     memset(vis, 0, sizeof(vis));  
  63.     for(int i = 0; i < n; ++i) d[i] = (i == 0 ? 0 : INF);  //初始化d数组  
  64.     for(int i = 0; i < n; ++i) {     //循环n次  
  65.         int m = INF, x;  
  66.         for(int y = 0; y < n; ++y) {     //在所有未标号的节点中,选出d值最小的节点x  
  67.             if(!vis[y] && d[y] <= m) m = d[x=y];  
  68.         }  
  69.         vis[x] = 1;  
  70.         for(int y = 0; y < n; ++y) {   //对于从x出发的所有边(x, y),更新d[y] = min(d[y], d[x]+maze[x][y])  
  71.             if(d[y] > d[x] + maze[x][y]) {  
  72.                 d[y] = d[x] + maze[x][y];  
  73.                 fa[y] = x;   //维护父亲指针  
  74.             }  
  75.         }  
  76.     }  
  77.     for(int i = 0; i < n; ++i) {  
  78.         cout << d[i] << endl;  
  79.         print_path1(i);  //打印路径方法1:从终点出发,不断顺着d[j] == d[i] + maze[i][j]的边(i, j)从节点j退回到节点i,直到回到起点。  
  80.         print_path2(i);  //打印路径方法2:空间换时间!在更新d数组的时候维护父亲指针!  
  81.     }  
  82.     return 0;  
  83. }  

(二)邻接表的建立

邻接表既可以用于有向图也可以用于无向图,在这种表示方法中,每个节点i都有一个链表,里面保存着从i出发的所有边,对于无向图来说,每条边会在邻接表中出现两次。

我们这里用数组实现链表:首先给每条边编号,然后用first[u]保存节点u的第一条边的编号,next[e]表示编号为e的边的“下一条边”的编号。

下面的代码针对有向图,建立邻接表。

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. #include <iostream>  
  2. using namespace std;  
  3.   
  4. const int MAXN = 1000;  
  5. int first[MAXN], next[MAXN], u[MAXN], v[MAXN], w[MAXN];  
  6.   
  7. int main() {  
  8.     int n, m;  
  9.     cin >> n >> m;  
  10.     for(int i = 0; i < n; ++i) first[i] = -1;   
  11.     for(int e = 0; e < m; ++e) {  
  12.         cin >> u[e] >> v[e] >> w[e];  
  13.         next[e] = first[u[e]];  
  14.         first[u[e]] = e;  
  15.     }  
  16.     for(int i = 0; i < n; ++i) cout << first[i] << endl;  
  17.     for(int e = 0; e < m; ++e) cout << next[e] << endl;  
  18.     return 0;  
  19. }  
上述代码的巧妙之处是插入到链表的首部而非尾部,这样就避免了对链表的遍历。在这里,同一个起点的各条边在邻接表中的顺序和读入顺序正好相反。

(三)使用邻接表跟优先队列的dijkstra。

queue跟priority_queue的唯一区别是,在优先队列中,元素并不是按照进入队列的先后顺序排列,而是按照优先级的高低顺序排列。pop()删除的是优先级最高的元素,而不一定是最先进入队列的元素。所以,获取对首元素的方法不再是front(),而是top()。

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. struct cmp{  
  2.     bool operator() (const int a, const int b) {  //a的优先级比b小时返回true  
  3.         return a % 10 > b % 10;  
  4.     }  
  5. };  
  6. priority_queue<int, vector<int>, cmp> q;  //“个位数大的优先级反而小”的整数优先队列  


声明一个小整数先出队列的优先队列:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. 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算法的代码如下:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. #include <iostream>  
  2. #include <queue>  
  3. using namespace std;  
  4.   
  5. const int MAXN = 1000;  
  6. const int MAXM = 100000;  
  7. const int INF = 100000000;  
  8. int n, m;  
  9. int first[MAXN], d[MAXN], done[MAXN];   //在寻找距离源点最近的点x过程中,done[i]表示第i个节点已经被处理过  
  10. int u[MAXM], v[MAXM], w[MAXM], next[MAXM];  
  11. typedef pair<intint> pii;  
  12.   
  13. int main() {  
  14.     cin >> n >> m;  
  15.     for(int i = 0; i < n; i++) first[i] = -1;  //初始化邻接表的表头  
  16.     for(int e = 0; e < m; ++e) {     //邻接表的建立  
  17.         cin >> u[e] >> v[e] >> w[e];  
  18.         next[e] = first[u[e]];  
  19.         first[u[e]] = e;  
  20.     }  
  21.     priority_queue< pii, vector<pii>, greater<pii> > q;  //用于在所有未处理过的节点中,选出d值最小的节点x  
  22.     memset(done, 0, sizeof(done));  //一开始假设所有节点都没有被处理过  
  23.     q.push(make_pair(d[0], 0));   //起点进入优先队列  
  24.     for(int i = 0; i < n; ++i) d[i] = (i == 0 ? 0 : INF);  
  25.     while(!q.empty()) {  
  26.         pii u = q.top();  
  27.         q.pop();  
  28.         int x = u.second;   //x表示当前d值最小的节点的节点号  
  29.         if(done[x]) continue;    //已经算过,忽略  
  30.         done[x] = 1;  
  31.         for(int e = first[x]; e != -1; e = next[e]) {   //遍历从x出发的所有边(x,y)更新d[y]  
  32.             if(d[v[e]] > d[x] + w[e]) {  
  33.                 d[v[e]] = d[x] + w[e];      //松弛成功,更新d[v[e]]  
  34.                 q.push(make_pair(d[v[e]], v[e]));  
  35.             }  
  36.         }  
  37.     }  
  38.     for(int i = 0; i < n; ++i) cout << d[i] << endl;   
  39.     return 0;  
  40. }  

(四)Bellman-Ford算法

当图中有负权的时候,最短路就不一定存在了,但是还是可以在最短路存在的情况下把它求出来。

如果最短路存在,则该最短路一定不含环!

原因:分为正环,零环,负环三种情况考虑!

如果是正环或零环,那最短路肯定不经过它!如果是负环,那肯定就不存在最短路了!

既然最短路不含环,那么该最短路就最多只经过n-1个节点(起点不算),所以可以通过n-1轮松弛操作得到!

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. #include <iostream>  
  2. using namespace std;  
  3.   
  4. const int MAXN = 1000;  
  5. const int INF = 100000000;  
  6. int n, m;  
  7. int d[MAXN], u[MAXN], v[MAXN], w[MAXN];  
  8.   
  9. int main() {  
  10.     cin >> n >> m;  
  11.     for(int e = 0; e < m; ++e) {  
  12.         cin >> u[e] >> v[e] >> w[e];  
  13.     }  
  14.     for(int i = 0; i < n; ++i) d[i] = INF;  
  15.     d[0] = 0;  
  16.     for(int k = 0; k < n-1; ++k) {   //迭代n-1次  
  17.         for(int e = 0; e < m; ++e) {   //检查每条边  
  18.             int x = u[e];  
  19.             int y = v[e];  
  20.             if(d[x] < INF) d[y] = min(d[y], d[x] + w[e]);  //松弛操作  
  21.         }  
  22.     }  
  23.     for(int i = 0; i < n; ++i) cout << d[i] << endl;  
  24.     return 0;  
  25. }  


用队列实现的话,效率会更高,像这样:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. #include<iostream>  
  2. #include<string>  
  3. #include<queue>  
  4. using namespace std;  
  5.   
  6. const int INF = 1000000000;  
  7. const int MAXN = 1000;  
  8. const int MAXM = 100000;  
  9.   
  10. int n, m;  
  11. int first[MAXN], d[MAXN];  
  12. int u[MAXM], v[MAXM], w[MAXM], next[MAXM];  
  13.   
  14. int main() {  
  15.     cin >> n >> m;  
  16.     for(int i = 0; i < n; ++i) first[i] = -1;  
  17.     for(int e = 0; e < m; ++e) {  
  18.         cin >> u[e] >> v[e] >> w[e];  
  19.         next[e] = first[u[e]];  
  20.         first[u[e]] = e;  
  21.     }  
  22.     queue<int> q;  
  23.     int inq[MAXN];  
  24.     for(int i = 0; i < n; ++i) d[i] = (i==0 ? 0 : INF);  
  25.     memset(inq, 0, sizeof(inq));  
  26.     q.push(0);  
  27.     while(!q.empty()) {  
  28.         int x = q.front(); q.pop();  
  29.         inq[x] = 0;    //标记x不在队列中  
  30.         for(int e = first[x]; e != -1; e = next[e]) if(d[v[e]] > d[x]+w[e]) {  
  31.             d[v[e]] = d[x] + w[e];  
  32.             if(!inq[v[e]]) {    //如果点v[e]不在队列中  
  33.                 inq[v[e]] = 1;   //标记点v[e]在队列中  
  34.                 q.push(v[e]);  
  35.             }  
  36.         }  
  37.     }     
  38.     for(int i = 0; i < n; i++)   cout << d[i] << endl;  
  39.     return 0;  
  40. }  

(五)Floyd算法

如果要求计算每两点之间的最短路:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. #include <iostream>  
  2. using namespace std;  
  3.   
  4. const int MAXN = 1000;  
  5. const int INF = 1000000;  
  6. int d[MAXN][MAXN];  
  7. int n;  
  8.   
  9. int main() {  
  10.     cin >> n;  
  11.     for(int i = 0; i < n; ++i) {  
  12.         for(int j = 0; j < n; ++j) {  
  13.             if(i == j) d[i][j] = 0;  
  14.             else d[i][j] = INF;  
  15.         }  
  16.     }  
  17.     for(int k = 0; k < n; ++k) {  
  18.         for(int i = 0; i < n; ++i) {  
  19.             for(int j = 0; j < n; ++j) {  
  20.                 if(d[i][j] < INF && d[k][j] < INF) d[i][j] = min(d[i][j], d[i][k]+d[k][j]);  
  21.             }  
  22.         }  
  23.     }  
  24.     for(int i = 0; i < n; ++i) {  
  25.         for(int j = 0; j < n; ++j) {  
  26.             cout << d[i][j] << endl;  
  27.         }  
  28.     }  
  29.     return 0;  
  30. }  
0 0