最短路问题总结
2014-04-03 23:40
218 查看
最短路问题可以分成单源最短路和全局最短路两类,下面分别介绍。
这个算法是通过为每个顶点 v 保留目前为止所找到的从起点s到v的最短路径来工作的。初始时,起点s到自身 的路径长度值被赋为 0 (d[s] = 0),若存在能直接到达的边 e(s,m) ,则把d[m]设为 e(s,m).value ,同时把所有其他(s不能直接到达的)顶点的路径长度设为无穷大,即表示我们不知道任何通向这些顶点的路径(对于图中所有顶点 v 除 s 和上述 m 外 d[v] = ∞)。当算法退出时,d[v] 中存储的便是从 s 到 v 的最短路径,或者如果路径不存在的话是无穷大。 Dijkstra
算法的基础操作是边的拓展:如果存在一条从 u 到 v 的边,那么从 s 到 v 的最短路径可以通过将边 e(u, v) 添加到尾部来拓展一条从 s 到 u 的路径。这条路径的长度是 d[u] + e(u, v).value 。如果这个值比目前已知的 d[v] 的值要小,我们可以用新值来替代当前 d[v] 中的值。拓展边的操作一直运行到所有的 d[v] 都代表从 s 到 v 最短路径的花费。这个算法经过组织因而当 d[u] 达到它最终的值的时候每条边 e(u, v) 都只被拓展一次。
算法维护两个顶点集 S 和 Q。集合 S 保留了我们已知的所有 d[v] 的值已经是最短路径的值顶点,而集合 Q 则保留其他所有顶点。集合S初始状态为空,而后每一步都有一个顶点从 Q 移动到 S。这个被选择的顶点是 Q 中拥有最小的 d[u] 值的顶点。当一个顶点 u 从 Q 中转移到了 S 中,算法对每条外接边 e(u, v) 都尝试了扩展。
邻接矩阵版本:
①图含有负边权的情况;
②需要判断无解(有负权环)的情况。
【算法流程】
算法大致流程是用一个队列来进行维护。 初始时将源加入队列。 每次从队列中取出一个元素,并对所有与他相邻的点进行松弛,若某个相邻的点松弛成功,则将其入队。 直到队列为空时算法结束。
这个算法,简单的说就是队列优化的bellman-ford,利用了每个点不会更新次数太多的特点发明的此算法。
SPFA——Shortest Path Faster Algorithm,它可以在O(kE)的时间复杂度内求出源点到其他所有点的最短路径,可以处理负边。SPFA的实现甚至比Dijkstra或者Bellman_Ford还要简单:
设 d 代表 s 到 i 点的当前最短距离,pre 代表 s 到 i 的当前最短路径中 i 点之前的一个点的编号。开始时 d 全部为 ∞ ,只有 d[s] = 0,pre全部为0。
维护一个队列,里面存放所有需要进行迭代的点。初始时队列中只有一个点 s 。用一个布尔数组记录每个点是否处在队列中。
每次迭代,取出队头的点 v ,依次枚举从 v 出发的边 v->u ,设边的长度为 c ,判断 d[v]+c 是否小于 d[u] ,若小于则改进 d[u] ,pre[u] = v,并且由于 s 到 u 的最短距离变小了,有可能 u 可以改进其它的点,所以若 u 不在队列中,就将它放入队尾。这样一直迭代下去直到队列变空,也就是 s 到所有的最短距离都确定下来,结束算法。若一个点入队次数超过n,则有负权环。
SPFA 在形式上和宽度优先搜索非常类似,不同的是宽度优先搜索中一个点出了队列就不可能重新进入队列,但是SPFA中一个点可能在出队列之后再次被放入队列,也就是一个点改进过其它的点之后,过了一段时间可能本身被改进,于是再次用来改进其它的点,这样反复迭代下去。设一个点用来作为迭代点对其它点进行改进的平均次数为k,有办法证明对于通常的情况,k在2左右。
SPFA算法(Shortest Path Faster Algorithm),也是求解单源最短路径问题的一种算法,用来解决:给定一个加权有向图 G 和源点 s ,对于图 G 中的任意一点 v ,求从 s 到 v 的最短路径。 SPFA算法是Bellman-Ford算法的一种队列实现,减少了不必要的冗余计算,他的基本算法和Bellman-Ford一样,并且用如下的方法改进:
1、第二步,不是枚举所有节点,而是通过队列来进行优化 设立一个先进先出的队列用来保存待优化的结点,优化时每次取出队首结点 u ,并且用 u 点当前的最短路径估计值对离开 u 点所指向的结点 v 进行松弛操作,如果 v 点的最短路径估计值有所调整,且 v 点不在当前的队列中,就将 v 点放入队尾。这样不断从队列中取出结点来进行松弛操作,直至队列空为止。
2、同时除了通过判断队列是否为空来结束循环,还可以通过下面的方法: 判断有无负环:如果某个点进入队列的次数超过 V 次则存在负环(SPFA无法处理带负环的图)。
①松弛
每次松弛操作实际上是对相邻节点的访问,第 n 次松弛操作保证了所有深度为 n 的路径最短。由于图的最短路径最长不会经过超过 V-1 条边,所以可知贝尔曼-福特算法所得为最短路径。
②负权环判定
因为负权环可以无限制的降低总花费,所以如果发现第 n 次操作仍可降低花销,就一定存在负权环。
【算法优化】
SPFA算法有两个优化算法 SLF 和 LLL:
SLF:Small Label First 策略,设要加入的节点是 j ,队首元素为 i ,若d[j] < d[i] ,则将 j 插入队首,否则插入队尾。
LLL:Large Label Last 策略,设队首元素为 i ,队列中所有 d 值的平均值为 x ,若d[i] > x 则将 i 插入到队尾,查找下一元素,直到找到某一 i 使得d[i] <= x,则将 i 出对进行松弛操作。
SLF 可使速度提高 15 ~ 20%;SLF + LLL 可提高约 50%。 在实际的应用中SPFA的算法时间效率不是很稳定,为了避免最坏情况的出现,只要没有负边权,通常使用效率更加稳定的Dijkstra算法。
邻接矩阵:
设图G中n 个顶点的编号为1到n。令 d[ i , j , k ] 表示从 i 到 j 的最短路径的长度,其中 k 表示该路径中的最大顶点,也就是说 d[ i , j , k ] 这条最短路径所通过的中间顶点最大不超过 k 。
因此:
若G中包含边,则 d[ i , j , 0 ] = 边的长度;
若 i = j ,则 d[ i , j , 0 ] = 0;
若G中不包含边,则 d[ i , j , 0 ] = +∞ 。 d[ i , j , n ] 则是从 i 到 j 的最短路径的长度。
对于任意的 k > 0 ,通过分析可以得到:
中间顶点不超过 k 的 i 到 j 的最短路径有两种可能:该路径含或不含中间顶点 k 。
若不含,则该路径长度应为 d[ i , j , k-1 ],否则长度为 d[ i , k , k-1 ]+d[ k , j , k-1 ]。d[ i , j , k ]可取两者中的最小值。
于是有DP的状态转移方程:[b]d[ i , j , k ] = min{ d[ i , j , k-1 ] , d[ i , k , k-1 ]+d[ k , j , k-1 ] },k>0。
这样,问题便具有了最优子结构性质,可以用动态规划方法来求解。
为了进一步理解,观察上面这个有向图:
若 k = 0, 1, 2, 3,则 d[ 1 , 3 , k ] = +∞ ,d[ 1 , 3 , 4 ] = 28;
若 k = 5, 6, 7,则 d[ 1 , 3 , k ] = 10;
若 k = 8, 9, 10,则 d[ 1 , 3 , k ] = 9。
因此1到3的最短路径长度为9。
不输出路径:
[b]这个算法还可以用来计算传递闭包(可达矩阵)
计算闭包只需将Floyd中的d数组改为布尔数组,将加号改为 “|” 就可以了。
单源最短路
单源最短路就是把图中某一个点当做起点,计算从起点到其余各点的最短路径。单源最短路的算法又因为图的特点分成两类:无负边权图的单源最短路和有负边权图的单源最短路。1、无负边权图的最短路——Dijkstra算法
这个算法是通过为每个顶点 v 保留目前为止所找到的从起点s到v的最短路径来工作的。初始时,起点s到自身 的路径长度值被赋为 0 (d[s] = 0),若存在能直接到达的边 e(s,m) ,则把d[m]设为 e(s,m).value ,同时把所有其他(s不能直接到达的)顶点的路径长度设为无穷大,即表示我们不知道任何通向这些顶点的路径(对于图中所有顶点 v 除 s 和上述 m 外 d[v] = ∞)。当算法退出时,d[v] 中存储的便是从 s 到 v 的最短路径,或者如果路径不存在的话是无穷大。 Dijkstra
算法的基础操作是边的拓展:如果存在一条从 u 到 v 的边,那么从 s 到 v 的最短路径可以通过将边 e(u, v) 添加到尾部来拓展一条从 s 到 u 的路径。这条路径的长度是 d[u] + e(u, v).value 。如果这个值比目前已知的 d[v] 的值要小,我们可以用新值来替代当前 d[v] 中的值。拓展边的操作一直运行到所有的 d[v] 都代表从 s 到 v 最短路径的花费。这个算法经过组织因而当 d[u] 达到它最终的值的时候每条边 e(u, v) 都只被拓展一次。
算法维护两个顶点集 S 和 Q。集合 S 保留了我们已知的所有 d[v] 的值已经是最短路径的值顶点,而集合 Q 则保留其他所有顶点。集合S初始状态为空,而后每一步都有一个顶点从 Q 移动到 S。这个被选择的顶点是 Q 中拥有最小的 d[u] 值的顶点。当一个顶点 u 从 Q 中转移到了 S 中,算法对每条外接边 e(u, v) 都尝试了扩展。
邻接矩阵版本:
#include<cstdio> #include<cstring> #include<queue> using namespace std; const int N = 101; const int INF = 0x7fffffff; //int型最大值 struct dis { dis( int V , int C ):v(V),c(C){} int v , c; bool operator < ( const dis &d )const //重载小于号,队列中升序排列 { return c > d.c; } }; int g , n , m , d ; //建立邻接矩阵、最短路径长度数组 //int pre ; //如果需要具体的最短路径则定义前驱记录数组 bool ok ; //已扩展的点集 void Dijkstra( int s , int e ) { priority_queue<dis> q; memset(ok,false,sizeof(ok)); for( int i = 0 ; i <= n ; i++ ) d[i] = INF; //初始化最短路径长度数组 d[s] = 0; //初始化起点 q.push(dis(s,0)); //起点进入待扩展队列 while(!q.empty()) //如果还有未扩展而可扩展的点 { if( ok[q.top().v] ) //扩展过的点跳过,并出队列 { q.pop(); continue; } int v = q.top().v; if( v == e ) return; //如果已经获得到终点的最短路径,跳出算法 ok[v] = true; //标记已扩展过的点 q.pop(); //出队列 for( int i = 1 ; i <= n ; i++ ) //在未确定的点中寻找newV相邻可扩展的点 if( !ok[i] && g[v][i] < INF ) //遍历可达的邻接点 { if( d[v]+g[v][i] < d[i] ) //如果可以更新 { d[i] = d[v]+g[v][i]; //更新邻接点到起点的距离 q.push(dis(i,d[i])); //入队列 //pre[i] = v; //更新邻接点的前驱(如果需要知道具体的最短路径) } } } } int main() { while( scanf("%d%d",&n,&m) , n||m ) { for( int i = 1 ; i <= n ; i++ ) for( int j = i ; j <= n ; j++ ) g[i][j] = g[j][i] = INF; //初始化邻接矩阵 for( int i = 0 ; i < m ; i++ ) { int a , b , c; scanf("%d%d%d",&a,&b,&c); if( g[a] > c ) //不查重可以去掉if g[a][b] = g[b][a] = c; //填写邻接矩阵 } Dijkstra(1,n); //输入起点和终点 printf("%d\n",d ); } return 0; }对应的邻接表版本:
#include<cstdio> #include<cstring> #include<vector> #include<queue> using namespace std; const int N = 101; const int INF = 0x7fffffff; //int型最大值 struct edge { edge(int V, int C):v(V),c(C){} int v , c; bool operator < ( const edge &e )const //重载小于号,队列中升序排列 { return c > e.c; } }; vector<edge> g ; //建立邻接表 int n , m , d ; //最短路径长度数组 //int pre ; //如果需要具体的最短路径则定义前驱记录数组 bool ok ; //已扩展的点集 void Dijkstra( int s , int e ) { priority_queue<edge> q; //这里借用edge类型来存v和d[v] memset(ok,false,sizeof(ok)); for( int i = 0 ; i <= n ; i++ ) d[i] = INF; //初始化最短路径长度数组 d[s] = 0; //初始化起点 q.push(edge(s,0)); //起点入队列 while(!q.empty()) //如果还有未扩展而可扩展的点 { if( ok[q.top().v] ) //扩展过的点跳过并出队列 { q.pop(); continue; } int v = q.top().v; if( v == e ) return; //如果已经获得到终点的最短路径,跳出算法 ok[v] = true; //标记已扩展过的点 q.pop(); //扩展过了就出队列 for( int i = 0 ; i < g[v].size() ; i++ ) //在未确定的点中寻找newV相邻可扩展的点 if( !ok[g[v][i].v] ) //遍历可达的邻接点 { int u = g[v][i].v; int c = g[v][i].c; if( d[v]+c < d[u] ) //如果可以更新 { d[u] = d[v]+c; //更新邻接点到起点的距离 //pre[u] = v; //更新邻接点的前驱(如果需要知道具体的最短路径) q.push(edge(u,d[u])); //入队列准备扩展 } } } } int main() { while( scanf("%d%d",&n,&m) , n||m ) { for( int i = 1 ; i <= n ; i++ ) g[i].clear(); //初始化邻接矩阵 for( int i = 0 ; i < m ; i++ ) { int a , b , c; scanf("%d%d%d",&a,&b,&c); g[a].push_back(edge(b,c)); g[b].push_back(edge(a,c)); } Dijkstra(1,n); //输入起点和终点 printf("%d\n",d ); } return 0; }
2、含负边权的最短路——SPFA算法
【适合处理】①图含有负边权的情况;
②需要判断无解(有负权环)的情况。
【算法流程】
算法大致流程是用一个队列来进行维护。 初始时将源加入队列。 每次从队列中取出一个元素,并对所有与他相邻的点进行松弛,若某个相邻的点松弛成功,则将其入队。 直到队列为空时算法结束。
这个算法,简单的说就是队列优化的bellman-ford,利用了每个点不会更新次数太多的特点发明的此算法。
SPFA——Shortest Path Faster Algorithm,它可以在O(kE)的时间复杂度内求出源点到其他所有点的最短路径,可以处理负边。SPFA的实现甚至比Dijkstra或者Bellman_Ford还要简单:
设 d 代表 s 到 i 点的当前最短距离,pre 代表 s 到 i 的当前最短路径中 i 点之前的一个点的编号。开始时 d 全部为 ∞ ,只有 d[s] = 0,pre全部为0。
维护一个队列,里面存放所有需要进行迭代的点。初始时队列中只有一个点 s 。用一个布尔数组记录每个点是否处在队列中。
每次迭代,取出队头的点 v ,依次枚举从 v 出发的边 v->u ,设边的长度为 c ,判断 d[v]+c 是否小于 d[u] ,若小于则改进 d[u] ,pre[u] = v,并且由于 s 到 u 的最短距离变小了,有可能 u 可以改进其它的点,所以若 u 不在队列中,就将它放入队尾。这样一直迭代下去直到队列变空,也就是 s 到所有的最短距离都确定下来,结束算法。若一个点入队次数超过n,则有负权环。
SPFA 在形式上和宽度优先搜索非常类似,不同的是宽度优先搜索中一个点出了队列就不可能重新进入队列,但是SPFA中一个点可能在出队列之后再次被放入队列,也就是一个点改进过其它的点之后,过了一段时间可能本身被改进,于是再次用来改进其它的点,这样反复迭代下去。设一个点用来作为迭代点对其它点进行改进的平均次数为k,有办法证明对于通常的情况,k在2左右。
SPFA算法(Shortest Path Faster Algorithm),也是求解单源最短路径问题的一种算法,用来解决:给定一个加权有向图 G 和源点 s ,对于图 G 中的任意一点 v ,求从 s 到 v 的最短路径。 SPFA算法是Bellman-Ford算法的一种队列实现,减少了不必要的冗余计算,他的基本算法和Bellman-Ford一样,并且用如下的方法改进:
1、第二步,不是枚举所有节点,而是通过队列来进行优化 设立一个先进先出的队列用来保存待优化的结点,优化时每次取出队首结点 u ,并且用 u 点当前的最短路径估计值对离开 u 点所指向的结点 v 进行松弛操作,如果 v 点的最短路径估计值有所调整,且 v 点不在当前的队列中,就将 v 点放入队尾。这样不断从队列中取出结点来进行松弛操作,直至队列空为止。
2、同时除了通过判断队列是否为空来结束循环,还可以通过下面的方法: 判断有无负环:如果某个点进入队列的次数超过 V 次则存在负环(SPFA无法处理带负环的图)。
①松弛
每次松弛操作实际上是对相邻节点的访问,第 n 次松弛操作保证了所有深度为 n 的路径最短。由于图的最短路径最长不会经过超过 V-1 条边,所以可知贝尔曼-福特算法所得为最短路径。
②负权环判定
因为负权环可以无限制的降低总花费,所以如果发现第 n 次操作仍可降低花销,就一定存在负权环。
【算法优化】
SPFA算法有两个优化算法 SLF 和 LLL:
SLF:Small Label First 策略,设要加入的节点是 j ,队首元素为 i ,若d[j] < d[i] ,则将 j 插入队首,否则插入队尾。
LLL:Large Label Last 策略,设队首元素为 i ,队列中所有 d 值的平均值为 x ,若d[i] > x 则将 i 插入到队尾,查找下一元素,直到找到某一 i 使得d[i] <= x,则将 i 出对进行松弛操作。
SLF 可使速度提高 15 ~ 20%;SLF + LLL 可提高约 50%。 在实际的应用中SPFA的算法时间效率不是很稳定,为了避免最坏情况的出现,只要没有负边权,通常使用效率更加稳定的Dijkstra算法。
邻接矩阵:
#include<cstdio> #include<cstring> #include<deque> using namespace std; const int N = 200; const int INF = 0x7fffffff; int g , n , m , d ; bool SPFA( int s , int e ) { int v , cnt = {0}; bool in_q = {false}; deque<int> q; for( int i = 0 ; i < n ; i++ ) d[i] = INF; //最短路径长初始化 q.push_back(s); //起点初始化 d[s] = 0; cnt[s]++; in_q[s] = true; while(!q.empty()) { v = q.front(); q.pop_front(); for( int i = 0 ; i < n ; i++ ) if( g[v][i] < INF && d[v]+g[v][i] < d[i] ) //松弛 { d[i] = d[v]+g[v][i]; if( !in_q[i] ) //入队列 { in_q[i] = true; cnt[i]++; if( cnt[i] > n ) //判断负权环 return false; if( !q.empty() && d[i] > d[q.front()] )//SLF优化 q.push_back(i); else q.push_front(i); } } in_q[v] = false; } return d[e] != INF; } int main() { int a , b , c; while( ~scanf("%d%d",&n,&m) ) { for( int i = 0 ; i < n ; i++ ) for( int j = 0 ; j <= i ; j++ ) g[i][j] = g[j][i] = INF; //邻接矩阵初始化 for( int i = 0 ; i < m ; i++ ) { scanf("%d%d%d",&a,&b,&c); if( g[a][b] > c ) g[a][b] = g[b][a] = c; } scanf("%d%d",&a,&b); printf("%d\n",SPFA(a,b)?d[b]:-1 ); } return 0; }邻接表(就不写注释了):
#include<cstdio> #include<cstring> #include<deque> #include<vector> using namespace std; const int N = 200; const int INF = 0x7fffffff; struct edge { edge( int V , int C ):v(V),c(C){} int v , c; }; vector<edge> g ; int n , m , d ; bool SPFA( int s , int e ) { int v , cnt = {0}; bool in_q = {false}; deque<int> q; for( int i = 0 ; i < n ; i++ ) d[i] = INF; q.push_back(s); d[s] = 0; cnt[s]++; in_q[s] = true; while(!q.empty()) { v = q.front(); q.pop_front(); for( int i = 0 ; i < g[v].size() ; i++ ) { int u = g[v][i].v; int c = g[v][i].c; if( d[v]+c < d[u] ) { d[u] = d[v]+c; if( !in_q[u] ) { in_q[u] = true; cnt[u]++; if( cnt[u] > n ) return false; if( !q.empty() && d[u] > d[q.front()] ) q.push_back(u); else q.push_front(u); } } } in_q[v] = false; } return d[e] != INF; } int main() { int a , b , c; bool in; while( ~scanf("%d%d",&n,&m) ) { for( int i = 0 ; i < n ; i++ ) g[i].clear(); for( int i = 0 ; i < m ; i++ ) { scanf("%d%d%d",&a,&b,&c); in = true; for( int j = 0 ; j < g[a].size() ; j++ ) if( g[a][j].v == b ) { in = false; if( g[a][j].c > c ) in = true; break; } if( in ) { g[a].push_back(edge(b,c)); g[b].push_back(edge(a,c)); } } scanf("%d%d",&a,&b); printf("%d\n",SPFA(a,b)?d[b]:-1 ); } return 0; }SPFA用邻接表,没有在内部解决重边的问题,只好事先做判断,虽然这对vector存储的邻接表来说很麻烦。
全局最短路
全局最短路即求图中每一点到其他所有点的最短路。不管是带负边权还是不带,只有Floyd算法这一种解决方案。Floyd-Warshall算法
Floyd-Warshall算法,简称Floyd算法,用于求解任意两点间的最短距离,时间复杂度为O(n^3)。我们平时所见的Floyd算法的一般形式如下:void Floyd() { int i,j,k; for( k = 1 ; k <= n ; k++ ) for( i = 1 ; i <= n ; i++ ) for( j = 1 ; j <= n ; j++ ) if( d[i][k]+d[k][j] < d[i][j] ) d[i][j] = d[i][k]+d[k][j]; }注意下第6行这个地方,如果 d[i][k] 或者 d[k][j] 不存在,程序中用一个很大的数代替。最好写成
if( d[i][k] != INF && d[k][j] != INF && d[i][k]+d[k][j] < d[i][j] )上面这个形式的算法其实是Floyd算法的精简版,而真正的Floyd算法是一种基于DP(Dynamic Programming)的最短路径算法。
设图G中n 个顶点的编号为1到n。令 d[ i , j , k ] 表示从 i 到 j 的最短路径的长度,其中 k 表示该路径中的最大顶点,也就是说 d[ i , j , k ] 这条最短路径所通过的中间顶点最大不超过 k 。
因此:
若G中包含边,则 d[ i , j , 0 ] = 边的长度;
若 i = j ,则 d[ i , j , 0 ] = 0;
若G中不包含边,则 d[ i , j , 0 ] = +∞ 。 d[ i , j , n ] 则是从 i 到 j 的最短路径的长度。
对于任意的 k > 0 ,通过分析可以得到:
中间顶点不超过 k 的 i 到 j 的最短路径有两种可能:该路径含或不含中间顶点 k 。
若不含,则该路径长度应为 d[ i , j , k-1 ],否则长度为 d[ i , k , k-1 ]+d[ k , j , k-1 ]。d[ i , j , k ]可取两者中的最小值。
于是有DP的状态转移方程:[b]d[ i , j , k ] = min{ d[ i , j , k-1 ] , d[ i , k , k-1 ]+d[ k , j , k-1 ] },k>0。
这样,问题便具有了最优子结构性质,可以用动态规划方法来求解。
为了进一步理解,观察上面这个有向图:
若 k = 0, 1, 2, 3,则 d[ 1 , 3 , k ] = +∞ ,d[ 1 , 3 , 4 ] = 28;
若 k = 5, 6, 7,则 d[ 1 , 3 , k ] = 10;
若 k = 8, 9, 10,则 d[ 1 , 3 , k ] = 9。
因此1到3的最短路径长度为9。
不输出路径:
#include<cstdio> const int N = 200; const int INF = 0x7fffffff; int g , n , m , d ; void Floyd() { int i , j , k; for( i = 0 ; i < n ; i++ ) for( j = 0 ; j < n ; j++ ) d[i][j] = g[i][j]; for( k = 0 ; k < n ; k++ ) for( i = 0 ; i < n ; i++ ) for( j = 0 ; j < n ; j++ ) if( d[i][k] < INF && d[k][j] < INF && d[i][k]+d[k][j] < d[i][j] ) d[i][j] = d[i][k]+d[k][j]; } int main() { int a , b , c; while( ~scanf("%d%d",&n,&m) ) { for( int i = 0 ; i < n ; i++ ) for( int j = 0 ; j <= i ; j++ ) g[i][j] = g[j][i] = j<i?INF:0; for( int i = 0 ; i < m ; i++ ) { scanf("%d%d%d",&a,&b,&c); if( g[a] > c ) g[a][b] = g[b][a] = c; } Floyd(); scanf("%d%d",&a,&b); printf("%d\n",d[a][b]<INF?d[a][b]:-1 ); } return 0; }输出路径:
#include<cstdio> const int N = 200; const int INF = 0x7fffffff; int g , n , m , d , pre , path , foot; void Floyd() { int i , j , k; for( i = 0 ; i < n ; i++ ) for( j = 0 ; j < n ; j++ ) { d[i][j] = g[i][j]; pre[i][j] = 0; } for( k = 0 ; k < n ; k++ ) for( i = 0 ; i < n ; i++ ) for( j = 0 ; j < n ; j++ ) if( d[i][k] < INF && d[k][j] < INF && d[i][k]+d[k][j] < d[i][j] ) { d[i][j] = d[i][k]+d[k][j]; pre[i][j] = k; } } void make_path( int i , int j ) { if( pre[i][j] > 0 ) { make_path( i , pre[i][j] ); make_path( pre[i][j] , j ); } else path[foot++] = j; } int main() { int a , b , c; while( ~scanf("%d%d",&n,&m) ) { for( int i = 0 ; i < n ; i++ ) for( int j = 0 ; j <= i ; j++ ) g[i][j] = g[j][i] = j<i?INF:0; for( int i = 0 ; i < m ; i++ ) { scanf("%d%d%d",&a,&b,&c); if( g[a][b] > c ) g[a][b] = g[b][a] = c; } Floyd(); scanf("%d%d",&a,&b); if( d[a][b] < INF ) { printf("cost:%d\n",d[a][b]); foot = 0; make_path(a,b); printf("path:%d->",a ); for( int i = 0 ; i < foot ; i++ ) printf("%d%s",path[i],i==foot-1?"\n":"->"); } else printf("no path\n"); } return 0; }
[b]这个算法还可以用来计算传递闭包(可达矩阵)
计算闭包只需将Floyd中的d数组改为布尔数组,将加号改为 “|” 就可以了。
for( int k = 0 ; k < n ; k++ ) for( int i = 0 ; i < n ; i++ ) for( int j = 0 ; j < n ; j++ ) d[i][j] |= d[i][k] && d[k][j];则d为可达矩阵。
相关文章推荐
- 图论总结(6)最短路问题
- 次短路,第k最短路,有限制的最短路问题总结
- 最短路问题(——模板习题与总结)
- GS,SEAN,WS 1210总结+1209计划
- .net中事务处理总结
- sysfs总结2
- ExtJS实战(10)-项目总结
- Sql Server 2005/2008 SqlCacheDependency查询通知的使用总结
- 基于PspCidTable的进程检测方法总结
- 线代总结5,6,7 特征向量,最小二乘,对称矩阵
- fstream、ifstream、ofsream使用的一点总结
- 【DOC】软件开发部部门年度工作总结
- H3C交换机2100配置总结
- 上拉电阻和下拉电阻总结
- Java基础—异常处理总结
- C# DEV GridView总结
- JS加强总结第一天(中实现省份之间的动态跳转案例)
- 关于数组的总结!
- perl DBI 总结