第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;

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

相关文章推荐

数据结构和算法——用动态规划求解最短路径问题

一、动态规划求解问题的思路 二、最短路径问题 三、利用动态规划求解最短路径

动态路径规划(一)

路径动态规划 华为机试, 笨笨熊搬家交通版题目 描述:## 森林里的苯苯熊要乔迁新喜,上次他已经将物品打包完成,并约了朋友来帮忙。接下来他要选定一个搬家的时间,想了很久,就决定在国庆节进行,因为国庆放...

矩阵乘法求最短路

算法导论矩阵乘法求最短路,如有错误望大家指出   #include #include #include #include using namespace std; #define...

POJ3613 经过K条边的最短路径 矩阵乘法 + floyd

以下选自:matrix67  十个利用矩阵乘法解决的经典问题 经典题目8 给定一个有向图,问从A点恰好走k步(允许重复经过边)到达B点的方案数modp的值     把给定的图转为邻接矩阵,即A(i...

主要的4种最短路算法

假如你有一张地图,地图上给出了每一对相邻城市的距离,从一个地点到另外一个地点,如何找到一条最短的路? 最短路算法要解决的就是这类问题。定义:给定一个有(无)向图,每一条边有一个权值 w,给定一个起始点...

poj3613Cow Relays (经过n条边的最短路)

//poj 3613 //求经过n条边的最短路 //此题让我更深入的了解了最短路的floyed算法 //首先对于一个图的可达矩阵A而言(可达为1,不可达为0),A表示是否直接可达。 //A*A表示是否...

数据结构.图.无向带权&邻接矩阵.最短路径Dijkstra算法

图的应用实在很广,课堂所学实为皮毛 考虑基于邻接矩阵的无向带权图,边的权值的典型意义就是路途的长度,从顶点u到顶点v的边权值为w,可以表示城市u到城市v之间路长为w。 最短路径问题考虑的就是从某个顶点...

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

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

求任意大小矩阵两点之间的最短路径(回溯)

任意给定两个正半轴坐标点,求最短路径。 给定起点和终点,求最短路径,一共有八个方向
  • BTnode
  • BTnode
  • 2016-04-09 03:14
  • 1115
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

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