最短路径
Dijkstra
迪杰斯特拉Dijkstra是求解单源最短路径的典型方法,指定源点s,求它到其余各个结点的最短路。思路是将所有结点分为两部分,一部分是已经确定最短路径的结点集P,剩余的结点集保存在Q中。初始时P中只有一个结点s,s距离Q中各个结点的距离dis初始化为INT_MAX。每次在Q中找到距离源点s最近的结点u,将u加入到集合P中,这样即确定了s到u的最短距离dis[u];同时更新由s到Q中剩余结点的最短距离,即对于Q中的特定结点v,比较原始的最短的路径值和经过了中间结点u的最短路径,取两者中的较小值:dis[v] = min(dis[v], dis[u]+matrix[u][v]),其中matrix为邻接矩阵。
如果需要记录到达某个结点的最短路径所经过的是哪些结点,那么需要维护path[i]这个数组,其中path[t]表示由源点s到达结点t最短路径中,t的前驱结点。如s->...->t0->t为s到t的最短路径,那么path[t]=t0,再去求解到达t0的最短路径,直到找到源点s。Dijkstra本质是一种贪心算法,无法处理有负权重的问题。
注:图片来源于https://www.jianshu.com/p/92e46d990d17
Floyd
动态规划。每次并入一个新顶点,看能否通过该顶点进行松弛。初始状态顶点i到顶点j的距离由邻接矩阵决定matrix[i][j],现在加入新的顶点,判断能否通过该顶点对matrix[i][j]进行松弛。如果matrix[i][k] + matrix[k][j] < matrix[i][j],说明顶点i先经过中间顶点k,然后再到顶点j比之间从顶点i到顶点j路程更短,松弛更新。依次并入剩余的所有顶点。
#include<iostream>
#include<vector>
#include<algorithm>
#include<limits>
using namespace std;
class Floyd {
public:
//构建邻接矩阵
Floyd(int n) : vertex_num(n), matrix(n, vector<int>(n, INT_MAX / 3)), path(n, vector<int>(n)) {
for (int i = 0; i < n; ++i) {
matrix[i][i] = 0;
}
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
//注意这里是j
path[i][j] = j;
}
}
}
void setEdge(int start, int end, int w) {
matrix[start][end] = w; //有向图
}
public:
int vertex_num;
private:
vector<vector<int>> matrix;
//path[i][j]表示由顶点i到顶点j经过的中间顶点(如果没有中间顶点则为目标顶点j)。
vector<vector<int>> path;
public:
//返回最短路径值
vector<vector<int>> floyd() {
int cnt = vertex_num;
//并入每个节点
for (int k = 0; k < cnt; ++k) {
for (int i = 0; i < cnt; ++i) {
for (int j = 0; j < cnt; ++j) {
if (matrix[i][k] + matrix[k][j] < matrix[i][j]) {
matrix[i][j] = matrix[i][k] + matrix[k][j];
//路径设置为经过下标为k 的顶点
path[i][j] = path[i][k];
}
}
}
}
return matrix;
}
//显示由顶点src到目标点dst的最短路径
void show_shortest_path(int src, int dst) {
if (src > vertex_num || dst > vertex_num) {
cout << "顶点超出范围:" << vertex_num - 1 << endl;
return;
}
int k = path[src][dst];
cout << src;
while (k != dst) {
cout << "->" << k;
k = path[k][dst];
}
cout << "->" << dst << ": " << matrix[src][dst] << endl;
}
};
class Dijkstra {
public:
Dijkstra(int n) : nvert(n),matrix(n, vector<int>(n, INT_MAX)){
for (int i = 0; i < n; ++i) {
matrix[i][i] = 0;
}
}
void setEdge(int start, int end, int w) {
matrix[start][end] = w; //有向图
}
public:
int nvert;
private:
vector<vector<int>> matrix;
vector<int> path; //path[i]记录的顶点i的前一个顶点,即path[i]->i
int src; //源顶点
public:
//返回由顶点src到各个顶点的最短距离
vector<int> dijkstra(int src) {
if (src >= nvert) {
cout << "src参数错误" << endl;
return vector<int>{};
}
int n = nvert;
vector<bool> visited(n, false);
visited[src] = true; //初始时P集合只有src这个节点
vector<int> dist(matrix[src].begin(), matrix[src].end());
path.resize(n, src);
//循环n-1次就能将Q遍历空
for (int time = 1; time < n; ++time) {
int min = INT_MAX;
int u = src;
//寻找Q中最近的结点,保存到u中
for (int i = 0; i < n; ++i) {
if (!visited[i] && dist[i] < min) {
min = dist[i];
u = i;
}
}
if (u == src) continue;
visited[u] = true; //将结点u加入到P集合
for (int v = 0; v < n; ++v) { //更新u的所有出边
//小心加法溢出
if (!visited[v] && matrix[u][v] != INT_MAX && dist[u] + matrix[u][v] < dist[v]) {
dist[v] = dist[u] + matrix[u][v];
path[v] = u;
}
}
}
this->src = src;
return dist;
}
//输出顶点src到dst最短路线
vector<int> show_shortest_path(int dst) {
if (dst >= nvert) {
cout << "dst参数错误" << endl;
return vector<int>{};
}
vector<int> shortest_path;
while (dst != src)
{
shortest_path.push_back(dst);
dst = path[dst];
}
shortest_path.push_back(src);
for (auto iter = shortest_path.rbegin(); iter != shortest_path.rend(); ++iter)
cout << "->" << *iter;
reverse(shortest_path.begin(), shortest_path.end());
return shortest_path;
}
};
int main() {
int n;
cin >> n;
Floyd floyd(n);
Dijkstra dijkstra(n);
int edge;
cin >> edge;
while (edge--)
{
int s, e, w;
cin >> s >> e >> w;
floyd.setEdge(s, e, w);
dijkstra.setEdge(s, e, w);
}
vector<vector<int>> res = floyd.floyd();
for (int i = 0; i < floyd.vertex_num; ++i)
floyd.show_shortest_path(0, i);
vector<int> dist = dijkstra.dijkstra(0);
dijkstra.show_shortest_path(5);
}
输入:
6
8
0 2 10
0 4 30
0 5 100
1 2 5
2 3 50
3 5 10
4 3 20
4 5 60
输出:
0->0: 0
0->1: 715827882
0->2: 10
0->4->3: 50
0->4: 30
0->4->3->5: 60
->0->4->3->5