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

原创 2016年05月31日 14:33:32

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

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

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

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

假设lmij为从结点i到结点j的至多包含m条边的任意路径中的最小权重。当m等于1时,不难发现l1ij=W,W为图边权重矩阵。对于m>1,我们需要计算的lmijlm1ij(从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(m<vertex_number-1){
                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;

                                        }
                        }
}
版权声明:本文为博主原创文章,未经博主允许不得转载。

笔试面试算法经典--矩阵的最短路径和(Java)

题目 给定一个矩阵m,从左上角开始每次只能向右或者向下走,最后到达右下角的位置,路径上所有的数字累加起来就是路径和,返回所有路径中最小的路径和。 例子: 给定m如下: 1 3 5 9 ...

算法导论-25.1-最短路径与矩阵乘法

一、介绍 二、代码 #include #include using namespace std; #define N 6//点的个数 #define M 10//边的个数 //邻...

基于矩阵实现的最短路径算法

1.最短路径 图中最短路径是指:寻找图(由结点和路径组成的)中两结点之间的权重最小的路径。Wikipedia上最短路径(Shortest Path)的定义如下: In graph theory, t...

矩阵中从左上角到右下角最短路径(五种方法)

题目:给定一个n*m的矩阵,矩阵中元素非负,从左上角到右下角找一条路径,使得路径上元素之和最小,每次只能向右或者向下走一个方格。如下图所示:最短路径是图中绿色部分的元素。 方法一(转换为...

最短路径(二)—Dijkstra算法(通过边实现松弛:邻接矩阵)

上一节通过Floyd-Warshall算法写了多源节点最短路径问题: http://blog.csdn.net/wtyvhreal/article/details/43315705 这一节来学习指...

Delphi7高级应用开发随书源码

  • 2003年04月30日 00:00
  • 676KB
  • 下载

Floyd算法实现每一对顶点之间的最短路径

之前介绍了Dijkstra算法求某个顶点到其他每个顶点之间的最短路径,那么求每一对顶点的最短路径呢?对,可以重复调用Dijkstra算法,Dijkstra算法的时间复杂度是O(n*n),调用n次,所以...

数据结构例程——每对顶点之间的最短路径

本文是[数据结构基础系列(7):图]中第14课时[每对顶点之间的最短路径]的例程。[Floyd算法实现] (程序中graph.h是图存储结构的“算法库”中的头文件,详情请单击链接…)#include...

图论(二):图的四种最短路径算法

本文总结了图的几种最短路径算法的实现:深度或广度优先搜索算法,弗洛伊德算法,迪杰斯特拉算法,Bellman-Ford算法 1),深度或广度优先搜索算法(解决单源最短路径) 从起始结点开始访...
  • qibofang
  • qibofang
  • 2016年06月06日 13:06
  • 18162

矩阵上寻找最短路径 Minimum Path Sum

题目源自于leetcode。图问题。 题目:Given a m x n grid filled with non-negative numbers, find a path from top left...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:第25章:每对顶点间的最短路径—基于矩阵乘法的动态规划算法
举报原因:
原因补充:

(最多只允许输入30个字)