算法设计初步之动态规划

简介: 算法设计初步之动态规划

理论知识


动态规划通常用来求解最优化问题。

最优化问题:这类问题的解可能有多个,每一个解对应一个值,我们希望寻找具有最优值的一个解。

适用于动态规划求解对最优子问题一般有两个要素:最优子结构和重叠子问题。

最优子结构是指:一个问题的最优解包含子问题的最优解;重叠子问题是指:问题的递归算法会反复求解相同的子问题

这里有一个要点,如何证明一个问题具有最优子结构性质?

我们采用复制—粘贴法:一般用反证法证明:假定子问题的解不是其自身的最优解,而存在“更优的解” ,那么我们可以从原问题的解中“剪切”掉这些“最优解”的部分,而将“更优的解”粘贴进去,从而得到原问题的一个“更优”解,这与最初的解是原问题最优解的前提假设相矛盾。因此,不可能存在“更优的解”。反之,原问题的子问题的解应是其自身的最优解——最优子结构性成立。


理解上面的知识之后,一般动态规划求解问题的步骤如下:

第一步:证明问题满足最优性原理

第二步:获得问题状态的递推关系式(即状态转移方程)

第三步:递归求解最优解的值

第四步:重构最优解

其中,状态转移方程的解释如下

状态转移方程: 第i+1阶段的状态变量x(i+1) 的值随x(i)和第i阶段的决策u(i)的值变化而变化,可以把这一关系看成(x(i),u(i))与x(i+1)的函数对应关系,用x(i+1)=Ti(x(i),u(i))表示。

我们也可以用子问题图来模拟递归求解过程

子问题图: 用于描述子问题与子问题之间的依赖关系。

值得注意,动态规划具有无后效性

无后效性: 对任意的阶段i,阶段i以后的行为仅依赖于i阶段的

状态,而与i阶段之前过程是如何达到这种状态的方式无关,这种性

质称为无后效性。


最后我们理解一下动态规划为什么能够给计算性能带来改变?

若问题的决策序列由n次决策构成,而每次决策有p种选择,若采用枚举法,则可能的决策序列将有p的n次方个。而利用动态规划策略的求解过程中仅保存了所有子问题的最优解,而舍去了所有不能导致问题最优解的次优决策序列,所以可能有多项式的计算复杂度。


一、钢条切割问题


1685018325417.jpg

实际该问题也是完全背包问题

第一步:我们假设dpi表示长度为i的钢条的最大收益,可以用反证法证明其划分的子问题dpj和dpi-j均具有最优子结构性(这个方法是两半都可能再次切割),这个问题还可以简化,如果划分的两半,一段可以继续切割记dpj,固定另一端值为ri-j,问题可以更简化

第二步:求递推方程,由上可以知道dpi可以划分为dpj和ri-j,则dpi就等于max(dpj+ri-j)其中,j从1到i

第三步:递归或者迭代求解最优解

1685018351567.jpg

1685018363691.jpg

两种方法实质就是递归求解和迭代求解的过程,一般推荐用第二种方法,由较小的子问题求解较大的子问题,这个时候上文所述的子问题图就可以发挥作用,通过它你可以清晰的了解你最先要求解的较小子问题是什么!


第四步:重构解

如果需要我们给出怎样切割的序列,这怎么办,利用上文记录的数组,如果dp[n]-r[i]=dp[n-i],说明肯定划分了i长的一段,这样就可以得出一个解


下文附自己实现的代码:

#include<iostream>
#include<algorithm> 
using namespace std;
const int maxl=1000;
int dp[maxl],p[maxl];
//dp[i]和p[i]分别表示长度为i的钢条的最大收益和价格 
int main() {
  int n;
  scanf("%d",&n);
  for(int i=1;i<=n;i++)
  scanf("%d",&p[i]);
  fill(dp,dp+n+1,0); //初始化
  for(int i=1;i<=n;i++) //迭代求解最优解过程 
  for(int j=1;j<=i;j++)
  dp[i]=max(p[j]+dp[i-j],dp[i]);
  printf("%d\n",dp[n]);
  while(dp[n]) { //求解最优解序列 
  for(int i=1;i<=n;i++) 
  if(dp[n]-p[i]==dp[n-i]){
    printf("%d ",i);
    n=n-i;
  } 
  }  
  return 0; 
}

1685018386570.jpg


二、 01背包问题


问题: 有n 个物品,它们有各自的重量和价值,现有给定容量的背包,如何让背包里装入的物品具有最大的价值总和?


分析:这个问题和上诉问题的区别在于每一个物品只能选一次或不选

最开始以dp[i][v]来表示前i件物品容量为v时的最大价格

第一步:分析最优性结构:对于dp[i][v],如果第i件不选,则dp[i-1][v]一定是最优,如果选了,dp[i-1][v-w[i]]一定是最优,否则反证之

第二步:如上分析,dp[i][v]=max(dp[i-1][v],dp[i-1][v-w[i]]+p[i]

第三步:迭代求解

第四步:重构解:


这里01背包问题还可以空间优化,但是对第四步重构解的求解有些不利,两种方法各有千秋


三、矩阵链乘法问题


1685018415047.jpg


首先理解题意,下面这段话方便理解

1685018424484.jpg

而采用不同的括号得到标量乘法次数不一样

我们记dp[i][j]表示第i个矩阵到第j个矩阵的最小标量

第一步:证明最优子结构性质,不妨设dp[i][j]的最大值在k处分开括号,那dp[i][k]和dp[k+1][j]也一定是最优方案不然反证之

第二步:如上,dp[i][j]显然和dp[i][k]和dp[k+1][j],前后两个形成的矩阵分布为piXpk,pkXpj,二者连起来又笑话pipjpk,所以状态转移方程可以列为min(dp[i][k]+dp[k+1][j]+pipjpk),k从i+1到j-1.

第三步:递归或迭代求解,迭代求解从小到大先求ij之间的间隔也可以越来越大

第四步:解重构,这个已经是二维,重构解最好在记录dp[i][j]时就用一个s[i][j]记录i到j矩阵最后一个分割的 位置k。


四、LCS问题


1685018439436.jpg

如上发问,我们记dp[i][j]为两个序列前i个和前j个的最长公共子序列

第一步:证明最优子结构,也顺路思考dp[i][j-1],dp[i-1][j],dp[i-1][j-1]之间的关系

1685018450746.jpg

第二步:求解递归方程,这里主要看待两个序列第i个字和第j个 的关系,如果相等,长度肯定是在dp[i-1][j-1]的基础是+1,如果不相等,则是max(dp[i][j-1],dp[i-1][j])

第三步:求解最优值

第四步:求解序列

1685018463231.jpg

从上面可以知道可以在构成dp数组是同时构造一个s数组,直接用三种不同的值代表上面三种情况。


五、最优二叉搜索树


1685018476460.jpg

1685018498391.jpg

上面就是问题描述:

这个问题的关键就是在找树根,先给一串给好的序列,假设取第k位为树根,则其划分为左右两部分。

第一步:证明最优结构性,即证明左右子树也是最优二叉搜索树,易证

第二步:dp[i][j]表示最优从i到j二叉搜索树的期望代价,那么上面的dp[1][k-1],dp[k+1][n]j加上树根k后代价是多少?多了一层所以加了树根代价是两边期望和和两者之间所有代价和,k从1到n,我们找这值最小的一个。

第三步:迭代求解

第四步:解重构


此外的Bellman-ford 算法,dag最长路(关键路径)算法也应用了动态规划。

相关文章
|
5天前
|
算法 开发者 Python
惊呆了!Python算法设计与分析,分治法、贪心、动态规划...这些你都会了吗?不会?那还不快来学!
【7月更文挑战第10天】探索编程巅峰,算法至关重要。Python以其易读性成为学习算法的首选。分治法,如归并排序,将大问题拆解;贪心算法,如找零问题,每步求局部最优;动态规划,如斐波那契数列,利用子问题解。通过示例代码,理解并掌握这些算法,提升编程技能,面对挑战更加从容。动手实践,体验算法的神奇力量吧!
26 8
|
7天前
|
算法 Python
算法不再难!Python分治法、贪心、动态规划实战解析,轻松应对各种算法挑战!
【7月更文挑战第8天】掌握Python算法三剑客:分治、贪心、动态规划。分治如归并排序,将大问题拆解递归解决;贪心策略在每步选最优解,如高效找零;动态规划利用子问题解,避免重复计算,解决最长公共子序列问题。实例展示,助你轻松驾驭算法!**
17 3
|
5天前
|
算法 Python
Python算法高手进阶指南:分治法、贪心算法、动态规划,掌握它们,算法难题迎刃而解!
【7月更文挑战第10天】探索Python算法的精华:分治法(如归并排序)、贪心策略(如找零钱问题)和动态规划(解复杂问题)。通过示例代码揭示它们如何优化问题解决,提升编程技能。掌握这些策略,攀登技术巅峰。
|
6天前
|
算法 程序员 Python
算法小白到大神的蜕变之路:Python分治法、贪心、动态规划,一步步带你走向算法巅峰!
【7月更文挑战第9天】探索算法之旅,以Python解锁编程高手之路。分治法如二分查找,将复杂问题拆解;贪心算法解决活动选择,每次选取局部最优;动态规划求斐波那契数列,避免重复计算,实现全局最优。每一步学习,都是编程能力的升华,助你应对复杂挑战,迈向算法大师!
12 1
|
6天前
|
存储 算法 Python
Python算法界的秘密武器:分治法巧解难题,贪心算法快速决策,动态规划优化未来!
【7月更文挑战第9天】Python中的分治、贪心和动态规划是三大关键算法。分治法将大问题分解为小问题求解,如归并排序;贪心算法每步选局部最优解,不保证全局最优,如找零钱;动态规划存储子问题解求全局最优,如斐波那契数列。选择合适算法能提升编程效率。
17 1
|
6天前
|
存储 算法 Python
震撼!Python算法设计与分析,分治法、贪心、动态规划...这些经典算法如何改变你的编程世界!
【7月更文挑战第9天】在Python的算法天地,分治、贪心、动态规划三巨头揭示了解题的智慧。分治如归并排序,将大问题拆解为小部分解决;贪心算法以局部最优求全局,如Prim的最小生成树;动态规划通过存储子问题解避免重复计算,如斐波那契数列。掌握这些,将重塑你的编程思维,点亮技术之路。
14 1
|
6天前
|
存储 算法 大数据
Python算法高手的必修课:深入理解分治法、贪心算法、动态规划,让你的代码更智能!
【7月更文挑战第9天】在Python算法学习中,分治法(如归并排序)将大问题分解为小部分递归解决;贪心算法(如货币找零)在每步选择局部最优解尝试达到全局最优;动态规划(如斐波那契数列)通过存储子问题解避免重复计算,解决重叠子问题。掌握这三种方法能提升代码效率,解决复杂问题。
|
7天前
|
算法 索引 Python
逆袭算法界!Python分治法、贪心算法、动态规划深度剖析,带你走出算法迷宫!
【7月更文挑战第8天】分治法,如快速排序,将大问题分解并合并解;贪心算法,选择局部最优解,如活动选择;动态规划,利用最优子结构避免重复计算,如斐波那契数列。Python示例展示这些算法如何解决实际问题,助你精通算法,勇闯迷宫。
16 1
|
7天前
|
算法 索引 Python
Python算法设计与分析大揭秘:分治法、贪心算法、动态规划...掌握它们,让你的编程之路更加顺畅!
【7月更文挑战第8天】探索Python中的三大算法:分治(如快速排序)、贪心(活动选择)和动态规划(0-1背包问题)。分治法将问题分解求解再合并;贪心策略逐步求局部最优;动态规划通过记忆子问题解避免重复计算。掌握这些算法,提升编程效率与解决问题能力。
15 1
|
7天前
|
算法 开发者 Python
惊!Python算法界的三大神器:分治法、贪心算法、动态规划,让你秒变算法大师!
【7月更文挑战第8天】在Python编程中,分治、贪心和动态规划是核心算法。分治如归并排序,将大问题拆解并递归求解;贪心算法针对找零问题,每次都选最大面额硬币,追求局部最优;动态规划则通过记忆化避免重复计算,如斐波那契数列。这些算法巧妙地提升效率,解决复杂问题。