动态规划-DAG最长路

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/xr469786706/article/details/87931868

动态规划-DAG最长路

DAG就是有向无环图,并且在之前已经讨论了如何求解DAG中的最长路,也就是所谓的”关键路径“。但是求解关键路径的做法对初学者来说确实有些复杂,而DAG上的最长路或者最短路问题又是特别重要的一类问题,很多问题都可以转换成求解DAG上的最长或最短路径问题,因此有必要介绍一下更简便的方法,也就是使用本节介绍的方法。由于DAG最长路和最短路的思想是一致的,因此下面以最长路为例。

本节着重解决两个问题:

  1. 求整个DAG中的最长路径(即不固定起点跟终点)
  2. 固定终点,求DAG的最长路径。

先讨论第一个问题:**给定一个有向无环图,怎样求解整个图的所有路径中权值之和最大的那条。**如图11-6所示,B->D->F->I就是该图的最长路径,长度为9。

针对这个问题,令dp[i]表示从i号顶点出发能获得的最长路径长度,这样所有dp[i]的最大值就是整个DAG的最长路径长度。

那么怎么求解dp数组呢?注意到dp[i]表示从i号顶点出发能获得的最长路径长度,如果从i号顶点出发能直接到达顶点j1,j2,…jk,而dp[j1],dp[j2],…,dp[jk]均已知,那么就有dp[i] = max{dp[j] + length[i->j]}

显然,根据上面的思路,需要按照逆拓扑排序来求解dp数组(想一想,为什么?)。但是有没有不求出逆拓扑序列也能计算dp数组的方法呢?当然有,那就是递归。请看下面的代码,其中图使用邻接矩阵的方式存储。

int DP(int i){
    if(dp[i] > 0) return dp[i];	// dp[i]已计算得到
    for(int j = 0 ; j < n ; j++){	// 遍历i的所有出边
        if(G[i][j] != INF){
            dp[i] = max(dp[i],DP(j)+G[i][j]);
        }
    }
    return dp[i];	// 返回计算完毕的dp[i]
}

由于从出度为0的顶点出发的最长路径长度为0,因此边界为这些顶点的dp值为0。但具体实现中不妨对整个dp数组初始化为0,这样dp函数当前访问的顶点i的出度为0时就会返回dp[i] = 0 (以此作为dp的边界),而出度不是0的顶点则会递归求解,递归过程中遇到已经计算过的顶点则直接返回对应的dp值,于是从程序逻辑上按照了逆拓扑序列的顺序进行。希望读者能认真体会上面代码的思想。

那么,如何知道最长路径具体是哪条呢?

回忆在Dijkstra算法中是如何求解最短路径的。-开了一个int型数组pre,来记录每个顶点的前驱,每当发现更短的路径时对pre进行修改。事实上,可以把这种想法应用于求解最长路径上-开一个int型,choice数组记录最长路径上顶点的后继顶点,这样就可以像Dijkstra算法中那样来求解最长路径了,只不过由于choice数组存放的是后继结点,因此使用迭代即可(当然使用递归也是可以的),如下面的代码所示。如果最终可能有多条最长路径,将choice数组改为vector类型的数组即可(也就是Dijkstra算法中有多条最短路径时的做法),代码留给读者实现。读者可以顺便思考一下如何求解最长路径的条数。

int DP(int i){
    if(dp[i] > 0) return dp[i];	// dp[i]已经得到
    for(int j = 0 ; j < n ; j++){
        if(G[i][j] != INF){
            int temp = DP(j) + G[i][j];	// 单独计算,防止if中调用DP函数两次
            if(temp > dp[i]){
                dp[i] = temp;	// 覆盖dp[i]
                choice[i] = j;	// 从i号顶点的后继顶点是j
            }
        }
    }
    return dp[i];	// 返回计算完毕的dp[i]
}
// 调用printPath前需要先得到最大的dp[i],然后将i作为路径起点传入
void printPath(int i){
    printf("%d",i);
    while(choice[i] != -1){// choice数组初始化为-1
        i = choice[i];
        printf("->%d",i);
    }
}

在上面讨论的基础上,接下来讨论本节开头的第二个问题:固定终点,求DAG得最长路径长度。例如在图11-6中,如果固定H为路径的终点,那么最长路径就会变成B->D->F->H。

int DP(int i){
    if(vis[i]) return dp[i];	// dp[i]已计算得到
    vis[i] = true;
    for(int j = 0 ; j < n ; j++){
        if(G[i][j] != INF){
            dp[i] = max(dp[i],DP(j)+G[i][j]);
        }
    }
    return dp[i];	// 返回计算完毕的dp[i]
}

猜你喜欢

转载自blog.csdn.net/xr469786706/article/details/87931868