码迷,mamicode.com
首页 > 编程语言 > 详细

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

时间:2016-07-15 21:54:32      阅读:595      评论:0      收藏:0      [点我收藏+]

标签:

二: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)ijd(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];
        }
}

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

标签:

原文地址:http://blog.csdn.net/weishenmetlc/article/details/51920423

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!