最短路
约 615 个字 157 行代码 预计阅读时间 5 分钟
Floyed¶
单元最广路线太复杂了,多源最短路还是可以用的
\(O(N^3)\)
C++
#include <cstring>
#include <iostream>
#define int long long
using namespace std;
int f[105][105];
signed main()
{
int n, m;
memset(f, 0x3f3f3f3f, sizeof(f));
cin >> n >> m;
for (int i = 1; i <= m; i++) {
int x, y, z;
cin >> x >> y >> z;
f[x][y] = f[y][x] = min(f[x][y], z);
}
for (int i = 1; i <= n; i++) {
f[i][i] = 0;
}
for (int k = 1; k <= n; k++) {
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
f[i][j] = min(f[i][j], f[i][k] + f[k][j]);
}
}
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
cout << f[i][j] << " ";
}
cout << endl;
}
}
Bellman–Ford 算法¶
可以找到单源最短路径,还能检测负环.
S是源点
利用松弛(relax)操作的最短路
即对于边\((u,v)\)尝试用\((S\rightarrow u \rightarrow v)\)更新长度
\(dis(v)=min(dis(v),dis(u)+w(u,v))\)
SPFA是Bellman–Ford 的一种实现
核心原理是每一轮松弛能到达的边
C++
struct Edge {
int u, v, w;
};
vector<Edge> edge;
bool bellmanford(int s){//s是源点
memset(d,inf,sizeof(d));//所有点的距离初始化为很大的整数
dis[s]=0;//自己本身的距离是0;
bool flag;//判断本轮循环中是否松弛;
for(int i=1;i<=n;i++){//最多n论,因为每次松弛都会让最短路径的长度加1,如果n轮后仍然可以松弛,说明有负环
flag=false;
for(int j=0;j<edge.size();j++){
int u=edge[j].u,v=edge[j].v,w=edge[j].w;
if(dis[u]==inf) continue;//还没有被更新的节点不可能是最短路径上的一点
if(dis[v]>dis[u]+w){
dis[v]=dis[u]+w;
flag=true;
}
}
if(!flag){
break;//没有边可以松弛了,意味着结束
}
}
return flag;//如果n论后flag还是true,说明有负环
}
算法的复杂程度是O(mn),
显然,可以队列优化,即SPFA,一般最短路还是迪杰斯特拉比较好
SPFA¶
C++
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN = 10005;
int n, m, s;
struct edge {
int u, v, w;
};
vector<edge> e[MAXN];
int dis[MAXN], cnt[MAXN], vis[MAXN]; // cnt最短路经过的边数
queue<int> q;
bool spfa(int n, int s)
{
for (int i = 1; i <= n; i++) {
dis[i] = (1ll << 31) - 1;
}
dis[s] = 0;
vis[s] = 1;
q.push(s);
while (!q.empty()) {
int u = q.front();
q.pop();
vis[u] = 0;
for (auto ele : e[u]) {
int v = ele.v, w = ele.w;
if (dis[v] > dis[u] + w) {
dis[v] = dis[u] + w;
cnt[v] = cnt[u] + 1;
if (cnt[v] >= n) return false;
if (!vis[v]) {
q.push(v);
vis[v] = 1;
}
}
}
}
return true;
}
signed main()
{
cin >> n >> m >> s;
for (int i = 1; i <= m; i++) {
int u, v, w;
cin >> u >> v >> w;
e[u].push_back({u, v, w});
}
spfa(n, s);
for (int i = 1; i <= n; i++) {
cout << dis[i] << " ";
}
return 0;
}
Dijkstra算法¶
C++
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 100005;
struct edge {
int v, w;
};
vector<edge> e[MAXN];
struct Polar {
int dis, u;
friend bool operator>(const Polar& a, const Polar& b) { return a.dis > b.dis; }
};
int dis[MAXN], vis[MAXN];
priority_queue<Polar, vector<Polar>, greater<Polar>> q;
void dijkstra(int n, int s)
{
memset(dis, 0x3f, sizeof(dis));
memset(vis, 0, sizeof(vis));
dis[s] = 0;
q.push({0, s});
while (!q.empty()) {
int u = q.top().u;
q.pop();
if (vis[u]) continue;
vis[u] = 1;
for (auto ele : e[u]) {
int v = ele.v, w = ele.w;
if (dis[v] > dis[u] + w) {
dis[v] = dis[u] + w;
q.push({dis[v], v});
}
}
}
}
int main()
{
int n, m, s;
cin >> n >> m >> s;
for (int i = 1; i <= m; i++) {
int u, v, w;
cin >> u >> v >> w;
e[u].push_back({v, w});
}
dijkstra(n, s);
for (int i = 1; i <= n; i++) {
cout << dis[i] << " ";
}
}
一些概念¶
路径 (Path)¶
在图论中,路径是顶点的一个序列,其中相邻顶点之间都有边连接。路径可以分为简单路径(顶点不重复)和一般路径(顶点可以重复)。
最短路 (Shortest Path)¶
最短路径是从图中的一个顶点到另一个顶点的路径,其中路径上的边的总权重最小。最短路径问题通常有不同的变种,取决于图的性质和具体需求。
有向图中的最短路 (Shortest Path in Directed Graph)¶
有向图中的最短路径问题是寻找从一个起点顶点到终点顶点的路径,其中路径上的边的方向必须被遵循。例如,给定顶点 u 和顶点 v,我们寻找一条从 u 到 v 的路径,使得路径上的边的总权重最小。
### 单源最短路 (Single Source Shortest Path)¶
单源最短路径问题是从图中的一个起点顶点(源点)出发,找到该顶点到图中所有其他顶点的最短路径。例如,Dijkstra算法和Bellman-Ford算法就是解决单源最短路径问题的经典算法。
无向图中的最短路 (Shortest Path in Undirected Graph)¶
无向图中的最短路径问题类似于有向图,但不同之处在于,边没有方向。因此,从顶点 uuu 到顶点 vvv 的路径可以沿任意方向行走,寻找路径上的边的总权重最小的路径。
负环¶
在图论中,负环是一个图中总权重为负数的环。具体来说,负环是指一条闭合路径,这条路径的边权重之和小于零
有负环可以无限减少路径