赞
踩
已知源点st,有向图G(无向图的每条边被视为两条有向边),求st到图中各个点的最短距离。
利用dis数组表示源点st到各个点的最短距离,初始时dis[st] = 0,其他为INF。
边的松弛:边a->b长度为c,如果dis[b]>dis[a] + c那么可以将dis[b]更新为dis[a] + c。
点的松弛:对以点v为起点的所有边进行边的松弛。
证明: 当每一条边都无法使用松弛操作更新dis数组时,dis数组达到最优。
其实所有的单源最短路算法都是以不同的顺序进行松弛操作,松弛操作的顺序也决定了算法的速度,如:Dijkstra算法的松弛顺序优于Bellman-Ford,也就是说Dijkstra算法可以以更少的松弛操作次数求得最优的dis数组。
需要证明:下列三种路径长度表达方式都能满足:“松弛的不能再松弛”的状态 与 dis数组达到最优互为充要条件
// 边起点为a 终点为b 权值为c 定义放松操作为: if (dis[b] > dis[a] * c) dis[b] = dis[a] * c 证明:当放松到无法放松时,dis数组最优 必要性:当dis数组最优时,如果还可以执行放松操作那么dis数组的值会变小, 这与dis数组最优矛盾。 // V为点V(i-1)->Vi的权值为Ci // V1为源点 充分性:对于任意一条最短路:V1->V2->V3-> .... V(k-1)->Vk有: dis[Vk] <= dis[V(k-1)] * Ck dis[V(k-1)] <= dis[V(k-2)] * C(k-1) : : dis[V3] <= dis[V2] * C3 dis[V2] <= dis[V1] * C2 将上述不等式相乘有:dis[Vk] <= Ck * C(k-1) * ... * C2 = 最优路径 已知权值路径为最短路所以:dis[Vk] >= 最优路径 所以dis[Vk] = 最优 得证。
最大值不等于最长路,最小值也不是最长路,这只是路径长度的一种表示方式,换句话说我们求得是某种路径长度表示下的最短路(例如:路径长度为权值最大值时,我们求得是让最大值最小)
// 边起点为a 终点为b 权值为c 不要求权值C > 0 定义放松操作为: if (dis[b] > max(dis[a], c)) dis[b] = max(dis[a], c) 证明:当放松到无法放松时,dis数组最优 必要性:当dis数组最优时,如果还可以执行放松操作那么dis数组的值会变小, 这与dis数组最优矛盾。 // V1, V2, ..... , V(k-1), Vk为点 // V(i-1)->Vi的权值为Ci // V1为源点 充分性:对于任意一条最短路:V1->V2->V3-> .... V(k-1)->Vk有: dis[Vk] <= max(dis[V(k-1)], Ck) dis[V(k-1)] <= max(dis[V(k-2)], C(k-1)) : : dis[V3] <= max(dis[V2], C3) dis[V2] <= max(dis[V1], C2) 将上述不等式进行变量的替换有: dis[Vk] <= max(Ck, C(k-1), ..... , C2) = 最优路径 已知权值路径为最短路所以:dis[Vk] >= 最优路径 所以dis[Vk] = 最优 得证。
同理不在证明
假设一个图G含有n个点,那么最短路所含有的边数最多为n - 1。
因为最短路不可能包含重复的点,所以最短路中的每条边都连接两个不同的点,所以最多包含n - 1条边,不然的话最短路就有大于n个的点了(除了一种情况那就是负环,存在负环的图的最短路是无限的,所以可以用Bellman-Ford算法在循环第n次dis数组还是发生了更新的时候说明存在负环)。
如果能在第i次循环找到包含i条边的最短路,那么只需要n - 1次循环就能找到所有的最短路(因为最短路所含有的路径数最长为n - 1)
// 定义边结构体 struct edge { int a; int b; int c; }; // 初始化dis数组 memset(dis, 0x3f, sizeof dis); dis[st] = 0; // 不断松弛 // n为图的点数 // m为图的边数 for (int i = 0; i < n - 1; ++ i) { for (int j = 0; j < m; ++ j) { // a -> b // 放松操作 int a = edges[j].a; int b = edges[j].b; int c = edges[j].c; if (dis[b] > dis[a] + c) dis[b] = dis[a] + c; } }
可以利用数学归纳法证明上述代码能在第i次循环找到包含i条边的最短路。
第一次循环能正确求出包含一条路径的最短路
假设第k次能求出所有包含k条路径的最短路
设包含k+1条路径的最短路V1->V2->V3-> … -> V(k+1) 那么V1->V2->V3-> … Vk 一定是最短路,并且已经在第k次循环中被求出来了,所以只要在利用Vk -> V(k+1) 这条边放松一下,这个包含k+1条路径的最短路就求得了。
在上述流程中可以发现大部分松弛是无意义的,比如在第一次循环中松弛能更新的只有和源点st相邻的点。更加一般的:在某一次循环中,只有dis[u]被更新过,那么以u为起点的边才可能进行有效松弛(如果上一次循环中没有更新dis[u],那么发现对以u为起点的边的放松操作在上一次循环和本次循环中是完全重复的),所以可以将更新的点u加入队列,不断更新。
code:
#include <iostream> #include <vector> #include <queue> #include <cstring> using namespace std; const int N = 2510, INF = 0x3f3f3f3f; struct edge { int to; int val; }; vector<edge> g[N]; // 邻接表 int dis[N]; // 出发点到各个点的距离 int n, m, st, ed; // 自左向右以此为:点个数,边个数,出发点,结束点 void spfa() { // 记录每个点是否在队列中,如果在需要加入点时发现队列中存在这个点那么无需加入。 bool vis[N] = {0}; queue<int> q; q.push(st); vis[st] = true; while(q.size()) { int u = q.front(); q.pop(); vis[u] = false; int sz = g[u].size(); for (int i = 0; i < sz; ++ i) { edge temp = g[u][i]; if (dis[temp.to] > dis[u] + temp.val) { dis[temp.to] = dis[u] + temp.val; if (!vis[temp.to]) q.push(temp.to); } } } } int main(void) { cin >> n >> m >> st >> ed; for (int i = 0; i < m; ++ i) { int a, b, c; cin >> a >> b >> c; //边起点,边终点,边长度 g[a].push_back({b, c}); // 有向图只加入一个 g[b].push_back({a, c}); // 无向图加入两个 } memset(dis, INF, sizeof dis); dis[st] = 0; // 队列优化的弗洛伊德算法:可以应对重边自环负边但无法应对负环 spfa(); // 应对出现负边的情况 if (dis[ed] <= INF/2) cout << dis[ed] << endl; // 无法到达 else cout << -1 << endl; return 0; }
code:
#include <iostream> #include <cstring> using namespace std; const int N = 100010, INF = 0x3f3f3f3f; struct edge { int i, j, val; }; edge e[N]; int n, m, k; int dist1[550], dist2[550]; void bellman_ford() { memset(dist1, INF, sizeof dist1); dist1[1] = 0; for (int i = 0; i < k; ++ i) { memcpy(dist2, dist1, sizeof(dist1)); for (int j = 0; j < m; ++ j) { int a = e[j].i; int b = e[j].j; int val = e[j].val; if (dist1[b] > dist2[a] + val) dist1[b] = dist2[a] + val; } } } int main(void) { cin >> n >> m >> k; for (int i = 0; i < m; ++ i) { int x, y, z; cin >> x >> y >> z; e[i] = {x, y, z}; } bellman_ford(); if (dist1[n] <= INF/2) cout << dist1[n]; else cout << "impossible"; return 0; }
code:
如何完整的解决第三部分中不同路径长度表示方法下的最短路问题?(拓展到不同形式的最短路问题)
证明松弛到无法松弛时,dis数组最优
证明算法能松弛到无法松弛
关于SPFA算法的好处: 第二点特别好证明,因为只要满足:在某一次循环中,只有dis[u]被更新过,那么以u为起点的边才可能进行有效松弛,SPFA算法结束时一定会达到无法松弛的状态。对比Bellman-Ford算法固定的循环n-1次,SPFA算法在队列为空时结束,此时一定无法靠松弛操作更新dis数组。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。