# 单源最短路问题
# 基本概念
# 初始化
初始化过程定义顶点的最短距离 d[v]
, 边权值矩阵 w[u][v]
和前驱数组 pi[v]
.
void initializeSingleSource(Graph G, int s) { | |
for (int v:G.vertices) { | |
d[v] = INT_MAX; | |
pi[v] = NULL; | |
} | |
d[s] = 0; // source | |
} |
# Bellman-Ford 算法
可以解决存在负权边情况下的单源最短路问题。
算法的核心是松弛 (relax) 技术。
int relax(int u,int v) { | |
if (d[v] > d[u] + w[u][v]) { | |
d[v] = d[u] + w[u][v]; | |
pi[v] = u; | |
} | |
} |
这样,Bellman-Ford 算法可以描述为下述代码:
bool BellmanFord(Graph G, int s) { | |
// initialize | |
initializeSingleSource(G, s); | |
for (auto v:G.vertices) { | |
for (auto e:G.edges) { | |
relax(e.from, e.to); | |
} | |
} | |
for (auto e:G.edges) { | |
int u = e.from; | |
int v = e.to; | |
if (d[v] > d[u] + w[u][v]) return false; | |
} | |
return true; | |
} |
可以看出,Bellman-Ford 算法的复杂度是 的。
需要注意的是,以 点为源点跑 Bellman–Ford 算法时,如果没有给出存在负环的结果,只能说明从 点出发不能抵达一个负环,而不能说明图上不存在负环。
因此如果需要判断整个图上是否存在负环,最严谨的做法是建立一个超级源点,向图上每个节点连一条权值为 的边,然后以超级源点为起点执行 Bellman–Ford 算法。
# SPFA
SPFA 是队列优化的 Bellman-Ford 算法。最差复杂度仍为 , 伪代码如下:
void SPFA(Graph G, int s) { | |
initializeSingleSource(G,s); | |
initializeQueue(Q); | |
Q.push(s); | |
while (!Q.empty()) { | |
u = Q.front(); | |
Q.pop(); | |
for (auto v : adj[u]) { | |
tmp = d[v]; | |
relax(u,v); | |
if (tmp != d[v] && !Q.has(v)) | |
Q.push(v); | |
} | |
} | |
} |
# Dijkstra 算法
是一种求解非负权图上单源最短路径的算法。
struct edge { | |
int v, w; | |
}; | |
vector<edge> e[maxn]; | |
int dis[maxn], vis[maxn]; | |
void Dijkstra(int n, int s) { | |
memset(dis, 63, sizeof(dis)); | |
dis[s] = 0; | |
for (int i = 1; i <= n; i++) { | |
int u = 0, mind = 0x3f3f3f3f; | |
for (int j = 1; j <= n; j++) | |
if (!vis[j] && dis[j] < mind) u = j, mind = dis[j]; | |
vis[u] = true; | |
for (auto ed : e[u]) { | |
int v = ed.v, w = ed.w; | |
if (dis[v] > dis[u] + w) dis[v] = dis[u] + w; | |
} | |
} | |
} |
这是暴力的写法,复杂度是 的。
如果对于稀疏图,采用二叉堆维护边的插入情况,那么复杂度可以优化到 . 对于稀疏图 (), 这是一个不错的优化。
如果采用斐波那契堆优化,那么复杂度可以降低到 .
# 多源最短路问题
# Floyd-Warshall 算法
只有三层 for
, 简单快捷 *。复杂度 .
for (k = 1; k <= n; k++) | |
for (x = 1; x <= n; x++) | |
for (y = 1; y <= n; y++) | |
f[k][x][y] = min(f[k - 1][x][y], f[k - 1][x][k] + f[k - 1][k][y]); |
是否存在负权,只需要观察 f[n][i][j]
中的对角线元素 ( i==j
) 中是否存在负元即可。
# Johnson 算法(稀疏图上)
复杂度 .
步骤如下:
- 添加超级源点 并初始化边。()
- 运行 Bellman-Ford 算法,判断是否存在负环。()
- 若不存在,根据 Bellman-Ford 计算的结果进行重赋权。()
- 对各节点,运行 Dijkstra. ().