本文讲述的是动态规划蕴涵的思想和编程性原理。将对动态规划使用的运筹学等等思想和编程上的实现原理进行剖析和总结。
本文较多引用百度百科词条。
2023/4/5更新:经过一年多的时间,我发现此文章存在一些细节上的问题,我会找时间修改,请注意甄别。
文章目的:帮助大家深层次了解动态规划的理论和编程思想。
目录
动态规划蕴涵的思想和编程性原理
一、思想出发
1.动态规划的总体概念引入
动态规划(Dynamic Programming,DP)是运筹学的一个分支,是求解决策过程最优化的过程。20世纪50年代初,美国数学家贝尔曼(R.Bellman)等人在研究多阶段决策过程的优化问题时,提出了著名的最优化原理,从而创立了动态规划。
——来源:动态规划.百度百科
基于上文,我们将会首先针对文中所提及的 最优化原理、 多阶段决策过程 以及这些过程 蕴含的其他必要前提 进行引入。
2.动态规划常见的编程疑惑
针对这一问题,这里引入 01背包问题
的代码(具体含义略):
注:
N
代表 物品个数
, V
代表 背包容量
node
包含单个物品的属性,w
代表 物品重量
,v
代表 物品价值
。
#include <bits/stdc++.h>
using namespace std;
const int len = 10010;
int N; // 物品个数
int V; // 背包容量
struct node{
int w; // 重量
int v; // 价值
}pool[len];
int dp[len][len], dp2[len]; // 二维数组解法 和 一维数组解法
// 打印 二维数组解法 的数组
void print_dp(){
for(int i = 1; i <= N; i++){
for(int j = 1; j <= V; j++){
cout << dp[i][j] << "\t";
}
cout << endl;
}
}
// 打印 一维数组解法 的数组
void print_dp2(){
for(int i = 1; i <= V; i++){
cout << dp2[i] << "\t";
}
cout << endl;
}
// 二维数组解法
int ans_2d(){
memset(dp, 0, sizeof(dp));
for(int i = 1; i <= N; i++){
for(int j = 0; j <= V; j++){
if(pool[i].w > j){
dp[i][j] = dp[i - 1][j];
}else{
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - pool[i].w] + pool[i].v);
}
print_dp();
}
}
return dp[N][V];
}
// 一维数组解法
int ans_1d(){
memset(dp2, 0, sizeof(dp2));
for(int i = 1; i <= N; i++){
for(int j = V; j >= pool[i].w; j--){
dp2[j] = max(dp2[j], dp2[j - pool[i].w] + pool[i].v);
print_dp2();
}
}
return dp2[V];
}
int main(){
cin >> N >>V;
for(int i = 1; i <= N; i++) cin >> pool[i].w >> pool[i].v;
cout << ans_2d() << endl << ans_1d() << endl;
return 0;
}
应该有不少人会对以上代码中的 状态转移方程为什么能用二维数组的操作表示 和 一维数组解法的合理性和原理 存疑。接下来将一一解答。
二、动态规划:术语
注:以下术语来源于百度百科
阶段:把所给求解问题的过程恰当地分成若干个相互联系的阶段,以便于求解,过程不同,阶段数就可能不同。描述阶段的变量称为阶段变量。在多数情况下,阶段变量是离散的,用 k 表示。
状态:状态表示每个阶段开始面临的自然状况或客观条件,它不以人们的主观意志为转移,也称为不可控因素。在上面的例子中状态就是某阶段的出发位置,它既是该阶段某路的起点,同时又是前一阶段某支路的终点。
决策:一个阶段的状态给定以后,从该状态演变到下一阶段某个状态的一种选择(行动)称为决策。
策略:由每个阶段的决策组成的序列称为策略。
子问题的重叠性:动态规划算法的关键在于解决冗余,这是动态规划算法的根本目的。动态规划实质上是一种以空间换时间的技术,它在实现的过程中,不得不存储产生过程中的各种状态,所以它的空间复杂度要大于其他的算法。选择动态规划算法是因为动态规划算法在空间上可以承受,而搜索算法在时间上却无法承受,所以我们舍空间而取时间。
三、动态规划:状态转移方程
给定 k 阶段状态变量 x(k) 的值后,如果这一阶段的决策变量一经确定,第 k+1 阶段的状态变量 x(k + 1) 也就完全确定,即 x(k + 1) 的值随 x(k) 和第 k 阶段的决策 u(k) 的值变化而变化,那么可以把这一关系看成 (x(k), u(k)) 与 x(k + 1) 确定的对应关系,用 x(k + 1) = Tk(x(k), u(k)) 表示。这是从 k 阶段到 k + 1 阶段的状态转移规律,称为状态转移方程。
动态转移方程是关于 状态 和 决策 的方程,即:
下一状态
=
T
k
(
当前状态
,
决策
)
下一状态=Tk(当前状态, 决策)
下一状态=Tk(当前状态,决策)
四、动态规划:条件
1.无后效性
无后效性是指如果在某个阶段上过程的状态已知,则从此阶段以后过程的发展变化仅与此阶段的状态有关,而与过程在此阶段以前的阶段所经历过的状态无关。利用动态规划方法求解多阶段决策过程问题,过程的状态必须具备无后效性。
——来源:动态规划.百度百科
也就是说,无后效性意味着 过程的历史只能通过当前的状态去影响它的未来的发展。从当前状态转移到下一个状态的决策以及当前状态的下一个状态仅与当前的状态有关,而与当前状态前的状态无关,当前的状态是对以往决策的总结。
2.最优化原理
作为整个过程的最优策略,它满足:相对前面决策所形成的状态而言,余下的子策略必然构成“最优子策略”。
即 最优策略的子策略也是最优。
3.注意
动态规划中的 阶段、决策、状态转移方程 都满足以上两个前提。
判断一个问题是否可以通过动态规划解决的前提也是以上两个条件。
五、动态规划:实现
使用动态规划求解问题,最重要的就是确定动态规划三要素:问题的 阶段,每个阶段的 状态 以及从前一个阶段转化到后一个阶段之间的 递推关系。
递推关系必须是从次小的问题开始到较大的问题之间的转化,从这个角度来说,动态规划往往可以用递归程序来实现,不过因为递推可以充分利用前面保存的子问题的解来减少重复计算,所以对于大规模问题来说,有递归不可比拟的优势,这也是动态规划算法的核心之处(同时注意动态规划是在用空间换时间)。
确定了动态规划的这三要素,整个求解过程就可以用一个最优决策表来描述,最优决策表是一个 二维表,其中 行表示决策的阶段,列表示问题状态,表格需要填写的数据一般对应此问题的在 某个阶段某个状态下的最优值(如最短路径,最长公共子序列,最大价值等),填表的过程就是根据 递推关系,从1行1列开始,以行或者列优先的顺序,依次填写表格,最后根据整个表格的数据通过简单的取舍或者运算求得问题的最优解。
六、对编程疑惑的解释
1.状态转移方程为什么能用二维数组的操作表示
01背包的二位决策表的第 i 行代表向背包中装入第 i 个物品这样一个 决策的阶段,第 j 列代表向容量为 j 的背包中装入第 i 个物品,即表示 i 这一决策阶段发生时的状态。
上文与前面的动态转移方程的解释结合《算法竞赛》(罗永军、郭卫斌著)第 124 ~ 125 页的决策表可以很容易理解这一点。
< 附:罗老师博客:罗永军的 优快云 主页 >
这里给出部分图:
2.一维数组解法的合理性和原理
上文中 决策表的引入 结合 无后效性 解释了为什么在01背包中 使用二维数组的合理性并且二位状态数组可以使用一维数组代替。因为当前状态仅与上一个状态有关,即表示决策表的第 i 行仅与第 i - 1 行有关,所以我们可以用一个一维数组代替原来的二维数组状态表。
STOP!
为防止因为刷短视频导致您的智能下降,请在评论区回答如下问题:
01背包的一维数组解法第二层 j 的循环换成了递减循环,这是为什么?
一点想法
作者在刚接触动态规划这一算法至今,对数学家和计算机学家们能用如此精妙的方式解决问题深深的感到震撼。印象最深刻的当属状态转移方程从数学到数据结构的精妙演变,真的很触动人心。
前几天阅读了一篇关于自然对数
e
e
e 的文章,惊叹于一个数字背后的奇妙原理和展现。
没有数学家和其他科学家们的贡献,人类一定只能过着山洞蜗居的生活吧!