1. floyd
- 描述
对于节点数为n
的无环无负权图,图中最远两个节点的最短距离最多经过n-1
条边。
子图 V ( 1 , k ) V(1,k) V(1,k)为除去 V V V除去边 e d g e ( i , j ) , ∀ i , j > k edge(i,j),\forall i,j \gt k edge(i,j),∀i,j>k后形成的子图。
对于允许通过子图 V ( 1 , k ) V(1,k) V(1,k),点 x , y x,y x,y间的最短路为
d
i
s
[
k
,
x
,
y
]
=
m
i
n
(
d
i
s
[
k
−
1
,
x
,
y
]
,
d
i
s
[
k
−
1
,
x
,
k
]
+
d
i
s
[
k
−
1
,
k
,
y
]
)
dis[k,x,y]=min(dis[k-1,x,y],dis[k-1,x,k]+dis[k-1,k,y])
dis[k,x,y]=min(dis[k−1,x,y],dis[k−1,x,k]+dis[k−1,k,y])
最短路即跑到
n
=
k
n=k
n=k
for (int k = 1;k <= n; ++k)
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= n; ++j)
dis[k][i][j] = min(dis[k -1][i][j], dis[k-1][i][k] + dis[k-1][k][j])
空间压缩,类似于背包问题的空间压缩。
for (int k = 1;k <= n; ++k)
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= n; ++j)
dis[i][j] = min(dis[i][j], dis[i][k] + dis[k][j])
时间复杂度 O ( n 3 ) O(n^3) O(n3),空间复杂度 O ( n 2 ) O(n^2) O(n2)。
2. bellman-ford
对每条边进行松弛操作。
即对于 e d g e ( u , v ) edge(u,v) edge(u,v)
d
i
s
[
v
]
=
m
i
n
(
d
i
s
[
v
]
,
d
i
s
[
u
]
+
w
e
i
g
h
t
[
u
]
[
v
]
)
dis[v]=min(dis[v], dis[u]+weight[u][v])
dis[v]=min(dis[v],dis[u]+weight[u][v])
对于无负环图的情况,最多经过
n
−
1
n-1
n−1次松弛;
所以对于 n n n次还可以松弛的情况说明了,存在负环。
struct Edge {
int u, v, w;
};
vector<Edge> edge;
int dis[MAXN], u, v, w;
const int INF = 0x3f3f3f3f;
bool bellmanford(int n, int s) {
memset(dis, 0x3f, sizeof(dis));
dis[s] = 0;
bool flag = false; // 判断一轮循环过程中是否发生松弛操作
for (int i = 1; i <= n; i++) {
flag = false;
for (int j = 0; j < edge.size(); j++) {
u = edge[j].u, v = edge[j].v, w = edge[j].w;
if (dis[u] == INF) continue;
// 无穷大与常数加减仍然为无穷大
// 因此最短路长度为 INF 的点引出的边不可能发生松弛操作
if (dis[v] > dis[u] + w) {
dis[v] = dis[u] + w;
flag = true;
}
}
// 没有可以松弛的边时就停止算法
if (!flag) {
break;
}
}
// 第 n 轮循环仍然可以松弛时说明 s 点可以抵达一个负环
return flag;
}
- spfa
松弛操作只可能发生在相邻的两条边,所以我们用队列进行优化。
struct edge {
int v, w;
};
vector<edge> e[maxn];
int dis[maxn], cnt[maxn], vis[maxn];
queue<int> q;
bool spfa(int n, int s) {
memset(dis, 63, sizeof(dis));
dis[s] = 0, vis[s] = 1;
q.push(s);
while (!q.empty()) {
int u = q.front();
q.pop(), vis[u] = 0;
for (auto ed : e[u]) {
int v = ed.v, w = ed.w;
if (dis[v] > dis[u] + w) {
dis[v] = dis[u] + w;
cnt[v] = cnt[u] + 1; // 记录最短路经过的边数
if (cnt[v] >= n) return false;
// 在不经过负环的情况下,最短路至多经过 n - 1 条边
// 因此如果经过了多于 n 条边,一定说明经过了负环
if (!vis[v]) q.push(v), vis[v] = 1;
}
}
}
return true;
}
3. dijstra
将结点分成两个集合:已确定最短路长度的点集(记为 S S S 集合)的和未确定最短路长度的点集(记为 T T T 集合)。一开始所有的点都属于 T T T 集合。
初始化 d i s ( s ) = 0 dis(s)=0 dis(s)=0,其他点的 d i s dis dis 均为 + ∞ +\infty +∞。
然后重复这些操作:
从 T 集合中,选取一个最短路长度最小的结点,移到 S 集合中。
对那些刚刚被加入 S 集合的结点的所有出边执行松弛操作。
直到 T 集合为空,算法结束。
堆优化,可以使用堆来进行插入,每次取堆上的最短长度节点。
struct edge {
int v, w;
};
struct node {
int dis, u;
bool operator>(const node& a) const { return dis > a.dis; }
};
vector<edge> e[maxn];
int dis[maxn], vis[maxn];
priority_queue<node, vector<node>, greater<node> > q;
void dijkstra(int n, int s) {
memset(dis, 63, sizeof(dis));
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 ed : e[u]) {
int v = ed.v, w = ed.w;
if (dis[v] > dis[u] + w) {
dis[v] = dis[u] + w;
q.push({dis[v], v});
}
}
}
}