赞
踩
目录
序号跟着上一篇(懂图的基础的也可以不看上一篇,很多竞赛还是会用到图的最短路径算法的):
Dijkstra算法(迪杰斯特拉算法)的基本思想如下:
以下是Dijkstra算法的动图演示:
以下是《算法导论》中Dijkstra算法的图解:
Dijkstra算法的实现:
下面是代码实现:
- #include <vector>
- #include <iostream>
- #include <string>
- #include <queue>
- #include <map>
- #include <set>
- #include <functional>
- using namespace std;
-
- namespace Matrix // 临接矩阵
- {
- template<class V, class W, W MAX_W = INT_MAX, bool Direction = false>
- class Graph //Vertex顶点,Weight权值,MAX_W不存在边的标识值 ,Direction有向无向
- {
- private:
- vector<V> _vertexs; // 顶点集合,保存已找到的最短路的顶点,顶点所在位置的下标作为该顶点的编号
- map<V, size_t> _vIndexMap; // 顶点映射下标
- vector<vector<W>> _matrix; // 存储边集合的矩阵,_matrix[i][j]表示编号为i和j的两个顶点之间的关系
-
- public:
- // 迪杰斯特拉算法,使用前提:图中所有边的权值非负。
- // 顶点个数是N -> 时间复杂度:O(N^2)空间复杂度:O(N)
- void Dijkstra(const V& src, vector<W>& dist, vector<int>& pPath)
- {
- size_t n = _vertexs.size();
- size_t srci = GetVertexIndex(src); // 获取源顶点的下标
- dist.resize(n, MAX_W); // 各个顶点的估计值初始化为MAX_W
- pPath.resize(n, -1); // 各个顶点的前驱顶点初始化为-1
-
- dist[srci] = 0; // 源顶点的估计值
- pPath[srci] = srci;
- vector<bool> S(n, false); // 已经确定最短路径的顶点集合
- for (size_t j = 0; j < n; ++j) // 将Q集合中的n个顶点全部加入到S集合
- {
- int u = 0; // 记录下一个选出来的顶点
- W min = MAX_W; // 最小估计值
- for (size_t i = 0; i < n; ++i) // 选最短路径顶点且不在S更新其他路径
- {
- if (S[i] == false && dist[i] < min)
- {
- u = i;
- min = dist[i];
- }
- }
- S[u] = true; // 将选出的顶点加入到S集合
- for (size_t v = 0; v < n; ++v) // 松弛更新u连接顶点v (srci->u) + (u->v) < srci->v 更新
- {
- if (S[v] == false && _matrix[u][v] != MAX_W && dist[u] + _matrix[u][v] < dist[v])
- {
- dist[v] = dist[u] + _matrix[u][v]; // 松弛更新出更小的路径权值
- pPath[v] = u; // 更新路径的前驱顶点
- }
- }
- }
- }
-
- // 打印最短路径及路径权值
- void PrintShortPath(const V& src, const vector<W>& dist, const vector<int>& pPath)
- {
- size_t srci = GetVertexIndex(src); // 获取源顶点的下标
- size_t n = _vertexs.size();
- for (size_t i = 0; i < n; ++i)
- {
- if (i != srci)
- {
- vector<int> path; // 找出i顶点的路径
- size_t parenti = i;
- while (parenti != srci)
- {
- path.push_back(parenti);
- parenti = pPath[parenti];
- }
- path.push_back(srci);
- reverse(path.begin(), path.end()); // 逆置
-
- for (auto index : path)
- {
- cout << _vertexs[index] << "->";
- }
- cout << "权值和:" << dist[i] << endl;
- }
- }
- }
- };
-
- void TestGraphDijkstra()
- {
- const char* str = "syztx";
- Graph<char, int, INT_MAX, true> g(str, strlen(str));
- g.AddEdge('s', 't', 10);
- g.AddEdge('s', 'y', 5);
- g.AddEdge('y', 't', 3);
- g.AddEdge('y', 'x', 9);
- g.AddEdge('y', 'z', 2);
- g.AddEdge('z', 's', 7);
- g.AddEdge('z', 'x', 6);
- g.AddEdge('t', 'y', 2);
- g.AddEdge('t', 'x', 1);
- g.AddEdge('x', 'z', 4);
-
- vector<int> dist;
- vector<int> parentpath;
- g.Dijkstra('s', dist, parentpath);
- g.PrintShortPath('s', dist, parentpath);
-
- // 图中带有负权路径时,贪心策略则失效了。
- // 测试结果可以看到s->t->y之间的最短路径没更新出来
- //const char* str = "sytx";
- //Graph<char, int, INT_MAX, true> g(str, strlen(str));
- //g.AddEdge('s', 't', 10);
- //g.AddEdge('s', 'y', 5);
- //g.AddEdge('t', 'y', -7);
- //g.AddEdge('y', 'x', 3);
- //vector<int> dist;
- //vector<int> parentPath;
- //g.Dijkstra('s', dist, parentPath);
- //g.PrintShortPath('s', dist, parentPath);
-
- //const char* str = "syztx";
- //Graph<char, int, INT_MAX, true> g(str, strlen(str));
- //g.AddEdge('s', 't', 6);
- //g.AddEdge('s', 'y', 7);
- //g.AddEdge('y', 'z', 9);
- //g.AddEdge('y', 'x', -3);
- //g.AddEdge('z', 's', 2);
- //g.AddEdge('z', 'x', 7);
- //g.AddEdge('t', 'x', 5);
- //g.AddEdge('t', 'y', 8);
- //g.AddEdge('t', 'z', -4);
- //g.AddEdge('x', 't', -2);
- //vector<int> dist;
- //vector<int> parentPath;
- //g.Dijkstra('s', dist, parentPath);
- //g.PrintShortPath('s', dist, parentPath);
- }
- }
Dijkstra算法原理:
Bellman-Ford(贝尔曼-福特算法)算法的基本思想如下:
Bellman-Ford算法的实现:
代码如下:
- namespace Matrix // 临接矩阵
- {
- template<class V, class W, W MAX_W = INT_MAX, bool Direction = false>
- class Graph //Vertex顶点,Weight权值,MAX_W不存在边的标识值 ,Direction有向无向
- {
- private:
- vector<V> _vertexs; // 顶点集合,顶点所在位置的下标作为该顶点的编号
- map<V, size_t> _vIndexMap; // 顶点映射下标
- vector<vector<W>> _matrix; // 存储边集合的矩阵,_matrix[i][j]表示编号为i和j的两个顶点之间的关系
-
- public:
- // 贝尔曼-福特算法,可以解决图中边的权值为负,但不能解决带负权回路
- // 时间复杂度:O(N^3) 空间复杂度:O(N)
- bool BellmanFord(const V& src, vector<W>& dist, vector<int>& pPath)
- {
- size_t n = _vertexs.size();
- size_t srci = GetVertexIndex(src); // 获取源顶点的下标
- dist.resize(n, MAX_W); // dist 记录srci - 其他顶点最短路径权值数组,初始化为MAX_W
- pPath.resize(n, -1); // pPath 记录srci - 其他顶点最短路径父顶点数组,初始化为-1
- dist[srci] = W(); // 先更新srci->srci为缺省值
-
- for (size_t k = 0; k < n - 1; ++k) // 总体最多更新n - 1轮
- {
- bool update = false; // 记录本轮是否更新过
- cout << "更新第:" << k << "轮" << endl;
- for (size_t i = 0; i < n; ++i)
- {
- for (size_t j = 0; j < n; ++j) // srci -> i + i ->j
- {
- if (_matrix[i][j] != MAX_W && dist[i] + _matrix[i][j] < dist[j])
- {
- cout << _vertexs[i] << "->" << _vertexs[j] << ":" << _matrix[i][j] << endl;
- dist[j] = dist[i] + _matrix[i][j]; // 松弛更新出更小的路径权值
- pPath[j] = i; // 更新路径的前驱顶点
- update = true;
- }
- }
- }
- if (update == false)
- break; // 如果这个轮次中没有更新出更短路径,那么后续轮次就不需要再走了
- }
-
- for (size_t i = 0; i < n; ++i) // 还能更新就是带负权回路
- {
- for (size_t j = 0; j < n; ++j) // srci -> i + i ->j
- {
- if (_matrix[i][j] != MAX_W && dist[i] + _matrix[i][j] < dist[j])
- {
- return false; // 带有负权回路的图无法求出最短路径
- }
- }
- }
- return true;
- }
- };
-
- void TestGraphBellmanFord()
- {
- //const char* str = "syztx";
- //Graph<char, int, INT_MAX, true> g(str, strlen(str));
- //g.AddEdge('s', 't', 6);
- //g.AddEdge('s', 'y', 7);
- //g.AddEdge('y', 'z', 9);
- //g.AddEdge('y', 'x', -3);
- //g.AddEdge('z', 's', 2);
- //g.AddEdge('z', 'x', 7);
- //g.AddEdge('t', 'x', 5);
- //g.AddEdge('t', 'y', 8);
- //g.AddEdge('t', 'z', -4);
- //g.AddEdge('x', 't', -2);
- //vector<int> dist;
- //vector<int> parentPath;
- //g.BellmanFord('s', dist, parentPath);
- //g.PrintShortPath('s', dist, parentPath);
-
- const char* str = "syztx";
- Graph<char, int, INT_MAX, true> g(str, strlen(str));
- g.AddEdge('s', 't', 6);
- g.AddEdge('s', 'y', 7);
- g.AddEdge('y', 'z', 9);
- g.AddEdge('y', 'x', -3);
- //g.AddEdge('y', 's', 1); // 新增
- g.AddEdge('z', 's', 2);
- g.AddEdge('z', 'x', 7);
- g.AddEdge('t', 'x', 5);
- g.AddEdge('t', 'y', 8);
- //g.AddEdge('t', 'y', -8); // 更改 8 -> -8
-
- g.AddEdge('t', 'z', -4);
- g.AddEdge('x', 't', -2);
- vector<int> dist;
- vector<int> parentPath;
- if (g.BellmanFord('s', dist, parentPath))
- g.PrintShortPath('s', dist, parentPath);
- else
- cout << "带负权回路" << endl;
- }
- }
为什么最多进行 n − 1 轮松弛更新?
从一个顶点到另一个顶点的最短路径中不能包含回路:
- 如果形成回路的各个边的权值之和为负数,则该回路为负权回路,找不到最短路径。
- 如果形成回路的各个边的权值之和为非负数,则多走这个回路是“徒劳”的,可能会使得路径长度变长。
在每一轮松弛过程中,后面路径的更新可能会影响到前面已经更新过的路径,比如使得前面已经更新过的路径的长度可以变得更短,或者使得某些源顶点之前不可达的顶点变得可达,但每一轮松弛至少能确定最短路径中的一条边,如果图中有 n 个顶点,那么两个顶点之间的最短路径最多有 n - 1 条边,因此最多需要进行 n - 1 次松弛更新。
例如下图中,顶点A,B,C,D,E的下标分别是0、1、2、3、4,现在要计算以顶点 E 为源顶点的单源最短路径。
对于上述图来说,Bellman-Ford算法在第一轮松弛的时候只能更新出 E -> D 这条边,在第二轮的时候只能更新出 D -> C,以此类推,最终就会进行4轮松弛更新(建议通过代码调试观察)。
Floyd-Warshall(弗洛伊德算法)算法的基本思想如下:
Floyd-Warshall算法解决的是任意两点间的最短路径的算法,其考虑的是路径的中间顶点,对于从顶点 i 到顶点 j 的路径来说,如果存在从顶点 i 到顶点 k 的路径,还存在从顶点 k 到顶点 j 的路径,并且这两条路径的权值之和小于当前从顶点 i 到顶点 j 的路径长度,则可以对顶点 j 的估计值和前驱顶点进行松弛更新。
Floyd-Warshall算法本质是一个简单的动态规划,就是判断从顶点 i 到顶点 j 的这条路径是否经过顶点 k,如果经过顶点 k 可以让这条路径的权值变得更小,则经过,否则则不经过。
Floyd-Warshall算法的实现:
代码如下:
- namespace Matrix // 临接矩阵
- {
- template<class V, class W, W MAX_W = INT_MAX, bool Direction = false>
- class Graph //Vertex顶点,Weight权值,MAX_W不存在边的标识值 ,Direction有向无向
- {
- private:
- vector<V> _vertexs; // 顶点集合,顶点所在位置的下标作为该顶点的编号
- map<V, size_t> _vIndexMap; // 顶点映射下标
- vector<vector<W>> _matrix; // 存储边集合的矩阵,_matrix[i][j]表示编号为i和j的两个顶点之间的关系
-
- public:
- //获取多源最短路径(弗洛伊德算法)
- void FloydWarshall(vector<vector<W>>& vvDist, vector<vector<int>>& vvpPath)
- {
- size_t n = _vertexs.size();
- vvDist.resize(n, vector<W>(n, MAX_W)); // 任意两个顶点直接的路径权值初始化为MAX_W
- vvpPath.resize(n, vector<int>(n, -1)); // 各个顶点的前驱顶点初始化为-1
-
- for (size_t i = 0; i < n; ++i) // 根据邻接矩阵初始化直接相连的顶点
- {
- for (size_t j = 0; j < n; ++j)
- {
- if (_matrix[i][j] != MAX_W) // i->j有边
- {
- vvDist[i][j] = _matrix[i][j]; // i->j的路径权值
- vvpPath[i][j] = i; // i->j路径的前驱顶点为i
- }
- if (i == j) // i->i
- {
- vvDist[i][j] = W(); // i->i的路径权值设置为权值的缺省值
- }
- }
- }
-
- // 最短路径的更新i-> {其他顶点} ->j
- for (size_t k = 0; k < n; ++k) // 依次取各个顶点作为i->j路径的中间顶点
- {
- for (size_t i = 0; i < n; ++i)
- {
- for (size_t j = 0; j < n; ++j)
- {
- // k 作为的中间点尝试去更新i->j的路径
- if (vvDist[i][k] != MAX_W && vvDist[k][j] != MAX_W
- && vvDist[i][k] + vvDist[k][j] < vvDist[i][j])
- // 存在i->k和k->j的路径,并且这两条路径的权值之和小于当前i->j路径的权值
- {
- vvDist[i][j] = vvDist[i][k] + vvDist[k][j]; // 松弛更新出更小的路径权值
-
- // 找跟j相连的上一个邻接顶点
- // 如果k->j 直接相连,上一个点就k,vvpPath[k][j]存就是k
- // 如果k->j 没有直接相连,k->...->x->j,vvpPath[k][j]存就是x
- vvpPath[i][j] = vvpPath[k][j]; // 更小路径的前驱顶点
- }
- }
- }
-
- // for (size_t i = 0; i < n; ++i) // 打印权值和路径矩阵观察数据
- // {
- // for (size_t j = 0; j < n; ++j)
- // {
- // if (vvDist[i][j] == MAX_W)
- // printf("%3c", '*');
- // else
- // printf("%3d", vvDist[i][j]);
- // }
- // cout << endl;
- // }
- // cout << endl;
-
- // for (size_t i = 0; i < n; ++i)
- // {
- // for (size_t j = 0; j < n; ++j)
- // {
- // printf("%3d", vvpPath[i][j]);
- // }
- // cout << endl;
- // }
- // cout << "=================================" << endl;
- }
- }
- };
-
- void TestFloydWarShall()
- {
- const char* str = "12345";
- Graph<char, int, INT_MAX, true> g(str, strlen(str));
- g.AddEdge('1', '2', 3);
- g.AddEdge('1', '3', 8);
- g.AddEdge('1', '5', -4);
- g.AddEdge('2', '4', 1);
- g.AddEdge('2', '5', 7);
- g.AddEdge('3', '2', 4);
- g.AddEdge('4', '1', 2);
- g.AddEdge('4', '3', -5);
- g.AddEdge('5', '4', 6);
- vector<vector<int>> vvDist;
- vector<vector<int>> vvParentPath;
- g.FloydWarshall(vvDist, vvParentPath);
-
- // 打印任意两点之间的最短路径
- for (size_t i = 0; i < strlen(str); ++i)
- {
- g.PrintShortPath(str[i], vvDist[i], vvParentPath[i]);
- cout << endl;
- }
- }
- }
下一篇是其它高阶数据结构④_LRU_Cache(概念+实现OJ)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。