第25章:每对顶点间的最短路径—基于矩阵乘法的动态规划算法

书中介绍了基于矩阵乘法的动态规划,floyd-warshall和Johnson这三种算法来解决单源最短路径问题。它们的适用情形如下:

算法 适用情形
基于矩阵乘法的动态规划 有向图(能应用于无向图,因为可以把无向图理解为有向图),边的权重可以为负值,不能有权重和为负值的环路
floyd-warshall 有向图(也能应用于无向图),边的权重可以为负值,不能存在权重和为负值的环路
Johsnon 有向图(也能应用于无向图),边的权重必须可以为负值,如果存在权重和为负值的环路,算法会探测到并报告出来

一:基于矩阵乘法的动态规划算法

该算法适用于边权重可以为负值,但不能有权重和为负值的环路。当不应用“重复平方”技术时,算法的运行时间为 Θ(V4) ;应用该技术时,算法的运行时间为 Θ(V3lgV)

假设 lmij 为从结点i到结点j的至多包含m条边的任意路径中的最小权重。当m等于1时,不难发现 l1ij=W ,W为图边权重矩阵。对于m>1,我们需要计算的 lmij lm1ij (从i到j最多由m-1条边组成的最短路径的权重)的最小值和从i到j最多由m条边组成的任意路径的最小权重,我们通过对j的所有可能前驱k进行检查来获得该值,因此递归定义 lmij=min(l(m1)ij,min1kn{l(m1)ik+wkj})=min1kn{l(m1)ik+wkj} 。因为结点i到结点j最多由n-1(n=|V|)条边组成,所以真正的最短路径 δ(i,j)=l(n1)ij=l(n)ij=l(n+1)ij=...

并且,我们可以在计算 lm 的同时计算前驱矩阵 predm 。具体来说,我们将计算一个矩阵序列 pred0,pred1,...,predn ,这里 predk 定义 predkij 为从结点i到结点j的至多包含k条边的任意路径中最短路径上j的前驱结点。当m=1时,如果i=j或者 wij= ,则 pred1ij=NIL ,否则的话 pred1ij=i 。当m大于1时,如果当k=j时,从i到j路径最段,则 predmij=predm1ij ,否则 predmij=k

未使用“重复平方技术”代码如下:

//给定矩阵smallest_length^(m-1)和W,返回smallest_length^m;
//pred[i][j]表示的是从i出发的某条最短路径上j的前驱顶点;
//给定矩阵pred^(m-1),返回pred^(m);
void extend_shortest_paths(const vector<vector<double>>& edge_weights,vector<vector<double>>& smallest_length,
vector<vector<int>>& pred)
{
        const int vertex_number=edge_weights.size();

        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,DBL_MAX);
                p[i].resize(vertex_number);
        }

        const int NIL=-1;
        for(int i=0;i!=vertex_number;++i)
                for(int j=0;j!=vertex_number;++j)
                {
                        int min_k=NIL; //NIL表示不存在的顶点;
                        for(int k=0;k!=vertex_number;++k)
                                if(smallest_length[i][k]!=DBL_MAX&&edge_weights[k][j]!=DBL_MAX){
                                        if(L[i][j]>smallest_length[i][k]+edge_weights[k][j]){
                                                L[i][j]=smallest_length[i][k]+edge_weights[k][j];
                                                min_k=k;
                                        }
                                }

                        if(min_k!=j)
                                p[i][j]=min_k;
                        else
                                p[i][j]=pred[i][j];
                }


        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];
                }
                }

void slow_all_pairs_shortest_paths(const vector<vector<double>>& edge_weights,vector<vector<double>>& smallest_length,
vector<vector<int>>& pred)
{
        const int vertex_number=edge_weights.size();

        const int NIL=-1; //NIL表示不存在的顶点;
        for(int i=0;i!=vertex_number;++i)
                for(int j=0;j!=vertex_number;++j)
                {
                        //smallest_length[i][j]表示的是从顶点i到j的至多包含1条边的任何路径的权值的最小值;
                        //pred[i][j]表示的是从顶点i到顶点j的最多包含1条边的最短路径中顶点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];
                }

        //为了求取从顶点i到j的路径权重最小值,m表示的是最短路径最多包含边的数目
        for(int m=2;m!=vertex_number;++m)
                extend_shortest_paths(edge_weights,smallest_length,pred);

}

使用“重复平方”技术可以减少计算最短路径权重矩阵的时间,但是不能同时计算前驱矩阵。代码如下:

void extend_shortest_paths(const vector<vector<double>>& edge_weights,
vector<vector<double>>& smallest_length)
{
        const int vertex_number=edge_weights.size();

        vector<vector<double>> L(vertex_number);
        for(int i=0;i!=L.size();++i)
                L[i].resize(vertex_number,DBL_MAX);

        for(int i=0;i!=vertex_number;++i)
                for(int j=0;j!=vertex_number;++j)
                        for(int k=0;k!=vertex_number;++k)
                                if(smallest_length[i][k]!=DBL_MAX&&edge_weights[k][j]!=DBL_MAX)
                                        if(L[i][j]>smallest_length[i][k]+edge_weights[k][j])
                                                L[i][j]=smallest_length[i][k]+edge_weights[k][j];


        for(int i=0;i!=vertex_number;++i)
                for(int j=0;j!=vertex_number;++j)
                        smallest_length[i][j]=L[i][j];
}

void fast_all_pairs_shortest_paths(const vector<vector<double>>& edge_weights,
vector<vector<double>>& smallest_length)
{
        const int vertex_number=edge_weights.size();

        const int NIL=-1; //NIL表示不存在的顶点;
        for(int i=0;i!=vertex_number;++i)
                for(int j=0;j!=vertex_number;++j)
                        //smallest_length[i][j]表示的是从顶点i到j的至多包含1条边的任何路径的权值的最小值;
                        smallest_length[i][j]=edge_weights[i][j];


        //为了求取从顶点i到j的路径权重最小值,m表示的是最短路径最多包含边的数目
        int m=1;
        while(m1){
                extend_shortest_paths(smallest_length,smallest_length);
                m=2*m;
        }
}

我们可以通过最短路径权重矩阵L来计算前驱矩阵pred,基本思想如下:假设从结点i到j存在着一条最短路径权重,结点j的前驱结点为k,则必然有L[i][j]=L[i][k]+w[k][j]。如此可以遍历L矩阵的第i行所有元素L[i][k],若L[i][j]=L[i][k]+w[k][j],则表明结点k是i->j最短路径中j的前驱结点。
代码如下:

//根据最短路径权重矩阵smallest_length计算前驱矩阵pred
void predecessor(const vector<vector<double>>& edge_weights,const vector<vector<double>>& smallest_length,
vector<vector<int>>& pred)
{
        const int NIL=-1;  //NIL表示不存在的顶点;
        const int vertex_number=edge_weights.size();

        for(int i=0;i!=vertex_number;++i)
                for(int j=0;j!=vertex_number;++j)
                        pred[i][j]=NIL;


        for(int i=0;i!=vertex_number;++i)
                for(int j=0;j!=vertex_number;++j)
                        if(i!=j&&smallest_length[i][j]!=DBL_MAX){
                                for(int k=0;k!=vertex_number;++k)
                                        if(smallest_length[i][k]!=DBL_MAX&&edge_weights[k][j]!=DBL_MAX)
                                                if(k!=j&&smallest_length[i][j]==smallest_length[i][k]+edge_weights[k][j]){
                                                        pred[i][j]=k;
                                                        break;

                                        }
                        }
}

你可能感兴趣的:(算法导论-CLRS)