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

相关文章

Java中JSON格式反序列化为Map且保证存取顺序一致的问题

《Java中JSON格式反序列化为Map且保证存取顺序一致的问题》:本文主要介绍Java中JSON格式反序列化为Map且保证存取顺序一致的问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未... 目录背景问题解决方法总结背景做项目涉及两个微服务之间传数据时,需要提供方将Map类型的数据序列化为co

Java Lambda表达式的使用详解

《JavaLambda表达式的使用详解》:本文主要介绍JavaLambda表达式的使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、前言二、Lambda表达式概述1. 什么是Lambda表达式?三、Lambda表达式的语法规则1. 无参数的Lambda表

详解如何使用Python构建从数据到文档的自动化工作流

《详解如何使用Python构建从数据到文档的自动化工作流》这篇文章将通过真实工作场景拆解,为大家展示如何用Python构建自动化工作流,让工具代替人力完成这些数字苦力活,感兴趣的小伙伴可以跟随小编一起... 目录一、Excel处理:从数据搬运工到智能分析师二、PDF处理:文档工厂的智能生产线三、邮件自动化:

Spring @RequestMapping 注解及使用技巧详解

《Spring@RequestMapping注解及使用技巧详解》@RequestMapping是SpringMVC中定义请求映射规则的核心注解,用于将HTTP请求映射到Controller处理方法... 目录一、核心作用二、关键参数说明三、快捷组合注解四、动态路径参数(@PathVariable)五、匹配请

git stash命令基本用法详解

《gitstash命令基本用法详解》gitstash是Git中一个非常有用的命令,它可以临时保存当前工作区的修改,让你可以切换到其他分支或者处理其他任务,而不需要提交这些还未完成的修改,这篇文章主要... 目录一、基本用法1. 保存当前修改(包括暂存区和工作区的内容)2. 查看保存了哪些 stash3. 恢

java String.join()方法实例详解

《javaString.join()方法实例详解》String.join()是Java提供的一个实用方法,用于将多个字符串按照指定的分隔符连接成一个字符串,这一方法是Java8中引入的,极大地简化了... 目录bVARxMJava String.join() 方法详解1. 方法定义2. 基本用法2.1 拼接

Java中的record使用详解

《Java中的record使用详解》record是Java14引入的一种新语法(在Java16中成为正式功能),用于定义不可变的数据类,这篇文章给大家介绍Java中的record相关知识,感兴趣的朋友... 目录1. 什么是 record?2. 基本语法3. record 的核心特性4. 使用场景5. 自定

如何解决Druid线程池Cause:java.sql.SQLRecoverableException:IO错误:Socket read timed out的问题

《如何解决Druid线程池Cause:java.sql.SQLRecoverableException:IO错误:Socketreadtimedout的问题》:本文主要介绍解决Druid线程... 目录异常信息触发场景找到版本发布更新的说明从版本更新信息可以看到该默认逻辑已经去除总结异常信息触发场景复

MyBatis编写嵌套子查询的动态SQL实践详解

《MyBatis编写嵌套子查询的动态SQL实践详解》在Java生态中,MyBatis作为一款优秀的ORM框架,广泛应用于数据库操作,本文将深入探讨如何在MyBatis中编写嵌套子查询的动态SQL,并结... 目录一、Myhttp://www.chinasem.cnBATis动态SQL的核心优势1. 灵活性与可

Python struct.unpack() 用法及常见错误详解

《Pythonstruct.unpack()用法及常见错误详解》struct.unpack()是Python中用于将二进制数据(字节序列)解析为Python数据类型的函数,通常与struct.pa... 目录一、函数语法二、格式字符串详解三、使用示例示例 1:解析整数和浮点数示例 2:解析字符串示例 3:解