第25章:所有结点对的最短路径问题—floyd-warshall和Johnson算法

二:Floyd-Warshall算法

该算法适用于边权重可以为负值,但环路权重和不能为负值的图,其运行时间为Θ(V3)。

假设dkij为从结点i到结点j的所有中间结点全部取自集合{1,2,…,k}的一条最短路径权重。当k=0时,从结点i到结点j的一条不包括编号大于0的中间结点的路径将没有任何中间结点。这样的路径最多只有一条边,因此d(0)ij=wij。因此如果k=0,dkij=wij,若k>=1,则dkij=min(d(k?1)ij,d(k?1)ik+d(k?1)kj)。

另外,我们可以在计算最短路径权重的同时计算前驱矩阵pred,具体来说,我们将计算一个矩阵序列,pred(0),pred(1)...pred(n),这里pred(k)ij为从结点i到结点j的一条所有中间结点都取自集合{1,2…,k}的最短路径上的j的前驱结点。当k=0时,从i到j的一条最短路径没有中间结点,因此如果i==j或wij=∞,则pred(0)ij=NIL,否则pred(0)ij=i。当k>=1时,如果d(ijk?1)≤d(k?1)ik+d(k?1)kj,则pred(k)ij=pred(k?1)ij,否则pred(k)ij=pred(k?1)kj。

代码如下:

void floyd_warshall(const vector<vector<double>>& edge_weights,vector<vector<double>>& smallest_length,vector<vector<int>>& pred)
{
        const int NIL=-1; //NIL表示不存在的顶点;
        const int vertex_number=edge_weights.size();

        //从顶点i到顶点j没有经过其它顶点时的smallest_length,所以此时为边的权重edge_weights;
        //pred表示此时对应的最短路径
        for(int i=0;i!=vertex_number;++i)
                for(int j=0;j!=vertex_number;++j)
                {
                        if(i==j||edge_weights[i][j]==DBL_MAX)
                                pred[i][j]=NIL;
                        else
                                pred[i][j]=i;

                        smallest_length[i][j]=edge_weights[i][j];
                }

        vector<vector<double>> L(vertex_number);
        vector<vector<double>> p(vertex_number);
        for(int i=0;i!=vertex_number;++i)
        {
                L[i].resize(vertex_number);
                p[i].resize(vertex_number);
        }
        for(int k=0;k!=vertex_number;++k)
        {
        //矩阵L表示中间顶点来自集合[0..k]时的顶点i到顶点j的最短路径权重和,矩阵p表示对应的路径;
        //smallest_length表示中间顶点来自集合[0..k-1]时的顶点i到顶点j的最短路径权重和,矩阵pred表示对应的路径。
                for(int i=0;i!=vertex_number;++i)
                        for(int j=0;j!=vertex_number;++j)
                                if(smallest_length[i][k]!=DBL_MAX&&smallest_length[k][j]!=DBL_MAX){
                                        if(smallest_length[i][j]<=smallest_length[i][k]+smallest_length[k][j]){
                                                p[i][j]=pred[i][j];
                                                L[i][j]=smallest_length[i][j];
                                        }
                                        else{
                                                p[i][j]=pred[k][j];
                                                L[i][j]=smallest_length[i][k]+smallest_length[k][j];
                                        }
                                }
                                else{
                                        L[i][j]=smallest_length[i][j];
                                        p[i][j]=pred[i][j];
                                }

        //把矩阵L和p分别赋值给smallest_length和pred,使得smallest_length此时
        //表示中间顶点来自集合[0..k]时的顶点i到顶点j的最短路径权重和,矩阵pred表示对应的路径
                for(int i=0;i!=vertex_number;++i)
                        for(int j=0;j!=vertex_number;++j)
                        {
                                smallest_length[i][j]=L[i][j];
                                pred[i][j]=p[i][j];
                        }
        }

}
有向图的传递闭包

给定有向图G=(V,E),结点集合V={1,2,..n},我们希望判断对于所有的结点对i和j,图G是否存在一条从结点i到结点j的路径。一种时间复杂度为Θ(n3)的计算图G的传递闭包的办法是给E的每条边赋予权重1,然后运行floyd-warshall算法。如果存在一条从结点i到结点j的路径,则有dij<n,否则dij=∞。

还有另一种类似的方法,其时间复杂度也是Θ(n3),但在实际场景中能够节省时间和空间。对于i,j,k=1,2,…n,我们定义:如果图G中存在一条从结点i到结点j的所有中间结点都取自集合{1,2,…,k}的路径,则t(k)ij=1,否则,t(k)ij=0,因此如果从i到j中存在着一条路径,则t(n)ij=1。类似的递归公式为,当k=0时,如果i==j或者wij≠∞,则t(0)ij=1,否则t0ij=0,对于k>=1,t(k)ij=t(k?1)ij||t(k?1)ik&&t(k?1)kj。

代码如下:

vector<vector<double>> transitive_closure(const vector<list<int>>& graph,
const vector<vector<double>>& edge_weights)
{
        const int vertex_number=graph.size();

        vector<vector<double>> t(vertex_number);
        vector<vector<double>> tmp(vertex_number);
        for(int i=0;i!=t.size();++i)
        {
                t[i].resize(vertex_number);
                tmp[i].resize(vertex_number);
        }

        for(int i=0;i!=vertex_number;++i)
                for(int j=0;j!=vertex_number;++j)
                        if(i==j||edge_weights[i][j]!=DBL_MAX)
                                t[i][j]=1;
                        else
                                t[i][j]=0;

        for(int k=0;k!=vertex_number;++k)
        {
                for(int i=0;i!=vertex_number;++i)
                        for(int j=0;j!=vertex_number;++j)
                                tmp[i][j]=t[i][j]||(t[i][k]&&t[k][j]);

                for(int i=0;i!=vertex_number;++i)
                        for(int j=0;j!=vertex_number;++j)
                                t[i][j]=tmp[i][j];
        }

        return t;
}

三:用于稀疏图的Johnson算法

Johnson算法要么返回一个包含所有结点对的最短路径权重的矩阵,要么报告输入图包含一个权重为负值的环路。

Johnson算法使用的技术成为重新赋予权重。该技术的工作原理如下:如果图G=(V,E)中所有边权重w皆为非负值,我们可以对每个结点运行一次Dijkstra算法来找到所有结点对之间的最短路径;如果图G包含权重为负值的边,但没有权重为负值的环路,那么只要计算出一组新的非负权重值,然后使用同样的方法即可。新赋予的权重必须满足下面两个重要性质:

1. 对于所有节点对(u,v),一条路径p是在使用原先权重函数时从结点u到结点v的一条最短路径,当且仅当p是在使用新权重函数时从u到v的一条最短路径;
2. 对于所有的边(u,v),新权重为非负值。

具体的操作过程见25.3节。代码如下:

//在已有图(假设有k个顶点)的基础上增加一个新的顶点,假设该新顶点的标号为k+1;
void add_source(vector<list<int>>& graph,vector<vector<double>>& edge_weights,vector<edge>& graph_edge)
{
        //链表adjacent_vertex中存储的是与新顶点相邻的顶点,新顶点与原先顶点全部相邻,但原先顶点不与新顶点相邻;
        const int new_vertex_label=graph.size();
        list<int> adjacent_vertex;
        for(int i=0;i!=graph.size();++i){
                adjacent_vertex.push_back(i);
                graph_edge.push_back(edge(new_vertex_label,i));
        }
        graph.push_back(adjacent_vertex);

        for(int i=0;i!=edge_weights.size();++i)
                edge_weights[i].push_back(DBL_MAX);  //原先图中顶点与新顶点不相邻;

        //新增加的顶点与相邻顶点的边的权重值为0,
        //这时候graph.size()已经包括了新增的顶点;
        vector<double> new_edges_weights(graph.size(),0);
        edge_weights.push_back(new_edges_weights);
}
void Johnson(vector<list<int>>& graph,vector<vector<double>>& edge_weights,vector<edge>& graph_edge,
vector<vector<double>>& smallest_length,vector<vector<int>>& pred)
{
        add_source(graph,edge_weights,graph_edge);

        const int new_vertex_label=graph.size()-1;

        //矢量smallest_weights存储的的是图中顶点到新增加顶点的最短路径权重,
        //矢量p存储的是对应的各个顶点的前驱结点;
        vector<double> smallest_weights(graph.size());
        vector<int> p(graph.size());
        if(bellman_ford(new_vertex_label,graph,edge_weights,graph_edge,smallest_weights,p)==false){
                cout<<"The input graph contains a negative-weight cycle"<<endl;
                return;
        }

        vector<double> h;
        for(int i=0;i!=smallest_weights.size();++i)
                h.push_back(smallest_weights[i]);

        for(int u=0;u!=edge_weights.size();++u)
                for(int v=0;v!=edge_weights[u].size();++v)
                        if(edge_weights[u][v]!=DBL_MAX)
                                edge_weights[u][v]+=h[u]-h[v]; //确保图中各个边的权重大于0,以便调用Dijkstra算法。

        //矩阵smallest_length存储的是原先图中结点对之间的最短路径权重;
        //矩阵pred存储的是对应路径中各个顶点的前驱结点。
        for(int u=0;u!=new_vertex_label;++u)
        {
                Dijkstra(u,graph,edge_weights,smallest_weights,p);

                for(int i=0;i!=new_vertex_label;++i)
                {
                        smallest_length[u][i]=smallest_weights[i];
                        pred[u][i]=p[i];
                }

                for(int v=0;v!=new_vertex_label;++v)
                        smallest_length[u][v]+=h[v]-h[u];
        }
}
时间: 2024-10-17 19:38:30

第25章:所有结点对的最短路径问题—floyd-warshall和Johnson算法的相关文章

九度 1447 最短路径 (Floyd算法和 Dijstra算法)

题目: 给出点的个数N.边的个数M(N<=100,M<=10000),以及M条边(每条边有3个整数A,B,C(1<=A,B<=N,1<=C<=1000),表A到B点的边权值为C).求点1到点N的最短路径长.N=M=0表示输入结束. Floyd算法: 1 #include <iostream> 2 #include <stdio.h> 3 #include <cstring> 4 using namespace std; 5 #defi

数据结构之---C语言实现最短路径之Floyd(弗洛伊德)算法

//此代码综合网络上的代码. //弗洛伊德算法Floyd代码 //杨鑫 #include <stdio.h> #include <stdlib.h> #define MAX_VERTEX_NUM 100 //最大顶点数 #define MAX_INT 10000 //无穷大 typedef int AdjType; typedef struct { int pi[MAX_VERTEX_NUM]; //存放v到vi的一条最短路径 int end; }PathType; typedef

《数据结构与算法分析:C语言描述》复习——第九章“图论”——无权值的最短路径问题

2014.07.04 18:24 简介: 给定一个有向图,你可以认为每条边长度都是1(所以叫无权值).下面的算法可以求出从特定的起点到终点的最短路径长度. 描述: 从起点出发,根据当前顶点出发的边进行广度优先搜索,直至找到终点即可.如果搜索结束了仍然没有找到终点,那么起点无法到达终点. 实现: 1 // A simple illustration for unweighted shortest path. Graph represented by adjacency matrix. 2 #inc

JavaScript高级程序设计(第三版)学习笔记22、24、25章

第22章,高级技巧 高级函数 安全的类型检测 typeof会出现无法预知的行为 instanceof在多个全局作用域中并不能正确工作 调用Object原生的toString方法,会返回[Object NativeConstructorName]格式字符串.每个类内部都有一个[[Class]]属性,这个属性中就指定了上述字符串中的构造函数名. 原生数组的构造函数名与全局作用域无关,因此使用toString方法能保证返回一致的值,为此可以创建如下函数: function isArray(value)

单源最短路径、所有结点对的最短路径

算法核心:两个结点之间的一条最短路径包含着(包含于)其它的最短路径.[最短路径性质] 1.单源最短路径Dijkstra 思路:计算每个结点到源结点的距离,压入最小优先队列Q,对Q中的元素进行如下循环操作: 1.从队列Q中弹出最小元素u 2.将u并入S 3.对u的邻接表中每个结点v,调用Relax(u,v,w)更新结点v到源结点s的距离 直至Q为空. 伪代码: Initialize-Single-Source(G,s) for each vertex v in G.v v.d = MAX v.p

第25章 CSS3过渡效果

第 25章 CSS3过渡效果学习要点:1.过渡简介2.transition-property3.transition-duration4.transition-timing-function5.transition-delay6.简写和版本 CSS3的过渡效果,通过这个功能可以不借助 JavaScript来实现简单的用户交互功能. 一.过渡简介过渡效果一般是通过一些简单的 CSS动作触发平滑过渡功能,比如::hover.:focus.:active.:checked等.CSS3提供了 trans

Lua_第25章 调用 C 函数

第25章 调用 C 函数  扩展 Lua 的基本方法之一就是为应用程序注册新的 C 函数到 Lua中去. 当我们提到 Lua 可以调用 C 函数,不是指 Lua 可以调用任何类型的 C 函数(有一些包可以让 Lua 调用任意的 C 函数,但缺乏便捷和健壮性).正如我们前面所看到的,当C 调用 Lua函数的时候,必须遵循一些简单的协议来传递参数和获取返回结果.相似的, 从Lua 中调用 C 函数,也必须遵循一些协议来传递参数和获得返回结果.另外,从 Lua 调用 C 函数我们必须注册函数,也就是说

利用Dijkstra算法实现记录每个结点的所有最短路径

最近在做PAT时发现图论的一些题目需要对多条最短路径进行筛选,一个直接的解决办法是在发现最短路径的时候就进行判断,选出是否更换路径:另一个通用的方法是先把所有的最短路径记录下来,然后逐个判断.前者具有一定的难度并且不好排查BUG,因此我设计了一种基于Dijkstra的记录所有最短路的简捷算法,用于解决此类题目. 我们知道,Dijkstra是解决单源最短路问题的,并且最基本的算法仅能求出最短路的长度,而不能输出路径,本文基于Dinjkstra进行改进,使之能记录源点到任意点的所有最短路径. 使用v

设计模式@第25章:策略模式

第25章:策略模式 一.编写鸭子项目,具体要求如下: 有各种鸭子(比如 野鸭.北京鸭.水鸭等, 鸭子有各种行为,比如 叫.飞行等) 显示鸭子的信息 二.传统方案解决鸭子问题的分析和代码实现 传统的设计方案(类图) 代码实现-看老师演示 Duck 抽象类 package com.gjxaiou.strategy; public abstract class Duck { public Duck() { } public abstract void display();//显示鸭子信息 public