DAG最长路问题详解

2024-06-16 14:44
文章标签 问题 详解 最长 dag

本文主要是介绍DAG最长路问题详解,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

DAG就是有向无环图,求解最长路,也就是所谓的关键路径。但是求解关键路径的方式比较复杂,而DAG上的最长路或者最短路问题又比较重要,很多问题都可以转换为求解DAG上的最长路或最短路问题。由于最长路和最短路的思想是一致的,因此下面以最长路为例:

主要分为两个问题:

(1)求整个DAG中的最长路径(即不固定起点和终点)

(2)固定终点,求DAG的最长路径

先解决第一个问题,给定一个有向无环图,怎样求解整个图的所有路径中权值之和最大的那条。

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

求解dp数组时注意到dp[i]表示从i号顶点出发能获得的最长路径长度,这个除了使用逆拓扑排序来做,可以使用递归的方法。

int dp(int i){if(dp[i]>0){return dp[i];}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];
}

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

如何知道最长路径是那条?

事实上可以仿照Dijkstra算法中求解最短路径的做法。开一个int型choice数组记录最长路径上顶点的后继顶点,这样就可以像Dijkstra算法中那样来求解最长路径了,只不过由于choice数组存放的是后继顶点,因此使用迭代即可。如果最终可能有多条最长路径,将choice数组改为vector类型的数组即可。

int dp(int i){if(dp[i]>0){return dp[i];}for(int j=0;j<n;j++){if(G[i][j]!=INF){int temp=dp(j)+G[i][j];if(temp>dp[i]){dp[i]=temp;choice[i]=j;//i号顶点的后继顶点是j }}}return dp[i]; 
}
void printPath(int i){printf("%d",i);while(choice[i]!=-1){i=choice;printf("->%d",i);}
}

对一般的动态规划问题而言,如果需要得到具体的最优方案,可以采用类似的方法,即记录每次决策所选择的策略,然后在dp数组计算完毕后根据具体情况进行递归或者迭代来获取方案。

求解最优方案时由于字典序的大小总是先根据序列中较前的部分来判断,因此序列中越靠前的顶点,其dp值应当越后计算(对一般的序列型动态规划问题也是如此)。

在上面讨论的问题上,接下来谈论第二个问题:固定终点,求DAG的最长路径长度。

此时假设规定的终点为T,那么可以令dp[i]表示从i号顶点出发到达终点T能获得的最长路径长度

这个问题和上面问题的区别是边界,在第一个问题中没有固定终点,因此所有出度为0的顶点的dp值为0是边界;但是这个问题固定了终点,因此边界应该为dp[T]=0。而初始化时dp数组不能初始化为0,因为从某些顶点出发可能无法到达终点T。合适的做法是初始化dp数组为一个负的大数,来保证无法到达终点的含义得以表达;然后设置一个vis数组表示顶点是否已经被计算。

int dp(int i){if(vis[i]){return 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];
}

记录方案及如何选择字典序最小的方案均与第一个问题相同。

这篇关于DAG最长路问题详解的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1066732

相关文章

使用Python删除Excel中的行列和单元格示例详解

《使用Python删除Excel中的行列和单元格示例详解》在处理Excel数据时,删除不需要的行、列或单元格是一项常见且必要的操作,本文将使用Python脚本实现对Excel表格的高效自动化处理,感兴... 目录开发环境准备使用 python 删除 Excphpel 表格中的行删除特定行删除空白行删除含指定

MySQL中的LENGTH()函数用法详解与实例分析

《MySQL中的LENGTH()函数用法详解与实例分析》MySQLLENGTH()函数用于计算字符串的字节长度,区别于CHAR_LENGTH()的字符长度,适用于多字节字符集(如UTF-8)的数据验证... 目录1. LENGTH()函数的基本语法2. LENGTH()函数的返回值2.1 示例1:计算字符串

Spring Boot spring-boot-maven-plugin 参数配置详解(最新推荐)

《SpringBootspring-boot-maven-plugin参数配置详解(最新推荐)》文章介绍了SpringBootMaven插件的5个核心目标(repackage、run、start... 目录一 spring-boot-maven-plugin 插件的5个Goals二 应用场景1 重新打包应用

mybatis执行insert返回id实现详解

《mybatis执行insert返回id实现详解》MyBatis插入操作默认返回受影响行数,需通过useGeneratedKeys+keyProperty或selectKey获取主键ID,确保主键为自... 目录 两种方式获取自增 ID:1. ​​useGeneratedKeys+keyProperty(推

Python通用唯一标识符模块uuid使用案例详解

《Python通用唯一标识符模块uuid使用案例详解》Pythonuuid模块用于生成128位全局唯一标识符,支持UUID1-5版本,适用于分布式系统、数据库主键等场景,需注意隐私、碰撞概率及存储优... 目录简介核心功能1. UUID版本2. UUID属性3. 命名空间使用场景1. 生成唯一标识符2. 数

Linux系统性能检测命令详解

《Linux系统性能检测命令详解》本文介绍了Linux系统常用的监控命令(如top、vmstat、iostat、htop等)及其参数功能,涵盖进程状态、内存使用、磁盘I/O、系统负载等多维度资源监控,... 目录toppsuptimevmstatIOStatiotopslabtophtopdstatnmon

java使用protobuf-maven-plugin的插件编译proto文件详解

《java使用protobuf-maven-plugin的插件编译proto文件详解》:本文主要介绍java使用protobuf-maven-plugin的插件编译proto文件,具有很好的参考价... 目录protobuf文件作为数据传输和存储的协议主要介绍在Java使用maven编译proto文件的插件

Android ClassLoader加载机制详解

《AndroidClassLoader加载机制详解》Android的ClassLoader负责加载.dex文件,基于双亲委派模型,支持热修复和插件化,需注意类冲突、内存泄漏和兼容性问题,本文给大家介... 目录一、ClassLoader概述1.1 类加载的基本概念1.2 android与Java Class

Java中的数组与集合基本用法详解

《Java中的数组与集合基本用法详解》本文介绍了Java数组和集合框架的基础知识,数组部分涵盖了一维、二维及多维数组的声明、初始化、访问与遍历方法,以及Arrays类的常用操作,对Java数组与集合相... 目录一、Java数组基础1.1 数组结构概述1.2 一维数组1.2.1 声明与初始化1.2.2 访问

SpringBoot线程池配置使用示例详解

《SpringBoot线程池配置使用示例详解》SpringBoot集成@Async注解,支持线程池参数配置(核心数、队列容量、拒绝策略等)及生命周期管理,结合监控与任务装饰器,提升异步处理效率与系统... 目录一、核心特性二、添加依赖三、参数详解四、配置线程池五、应用实践代码说明拒绝策略(Rejected