动态规划算法的表达技巧分析
- 格式:doc
- 大小:35.00 KB
- 文档页数:7
算法设计与分析中的动态规划动态规划是一种常用的算法设计与分析技术,通常用于求解具有重叠子问题和最优子结构性质的问题。
它的核心思想是将原问题分解为更小的子问题,并通过递推关系式将子问题的解整合为原问题的解。
在算法设计与分析领域,动态规划广泛应用于优化问题、最短路径问题、序列比对问题等。
一、动态规划的基本特征动态规划算法的正确性基于两个重要的特征:重叠子问题和最优子结构。
1. 重叠子问题重叠子问题是指在求解原问题时,子问题之间存在相互重叠的情况。
也就是说,子问题之间不是独立的,它们具有一定的重复性。
动态规划算法利用这个特征,通过保存已经求解过的子问题的解,避免重复计算,提高算法的效率。
2. 最优子结构最优子结构是指问题的最优解可以通过子问题的最优解推导得到。
也就是说,原问题的最优解可以通过一系列子问题的最优解进行构造。
这个特征是动态规划算法能够求解最优化问题的关键。
二、动态规划的基本步骤1. 确定状态动态规划算法需要明确问题的状态,即问题需要用哪些参数来描述。
状态一般与原问题和子问题的解相关。
2. 定义状态转移方程状态转移方程描述原问题与子问题之间的关系。
通过递推关系式,将原问题分解为更小的子问题,并将子问题的解整合为原问题的解。
3. 初始化根据问题的实际需求,对初始状态进行设定,并计算出初始状态的值。
这一步骤是递推关系式的起点。
4. 递推计算根据状态转移方程,通过递推关系式计算出子问题的解,并将子问题的解整合为原问题的解。
这一步骤通常采用迭代的方式进行。
5. 求解目标问题通过递推计算得到原问题的解,即为最优解或者问题的答案。
三、动态规划的应用动态规划算法在实际问题中具有广泛的应用。
下面以两个经典问题为例,介绍动态规划在实际中的应用。
1. 背包问题背包问题是一种经典的优化问题,主要包括0/1背包问题和完全背包问题。
其核心思想是在限定的背包容量下,选择一些具有最大价值的物品放入背包中。
2. 最长公共子序列问题最长公共子序列问题是指给定两个序列,求解它们的最长公共子序列的长度。
动态规划算法的实现及其应用动态规划,英文缩写为 DP,是一种算法设计技术,通常用于求解最优化问题。
动态规划是解决一类特殊问题的有效方法。
它通过将原问题转化为若干个子问题的方式,逐个求解这些子问题,最终得到原问题的解。
这种方式具有很强的适用性,能够解决很多实际问题。
动态规划的实现动态规划算法的实现基本上可以分为以下两个步骤:1. 确定状态:将原问题转化为若干个子问题,定义合适的状态量来表示子问题。
状态的定义应该满足无后效性,即状态一旦确定,之后的状态转移不会再受之前的状态影响。
2. 确定状态转移方程:定义状态转移方程,通过状态之间的转移来逐步求解原问题。
状态转移方程可以通过一些简单的规律得到,也可以通过数学方法进行求解。
动态规划的应用动态规划算法有很多应用,下面列举一些常见的应用场景。
1. 最长公共子序列问题:给定两个字符串,求出它们的最长公共子序列,即在两个字符串中都出现的、长度最长的子序列。
这个问题可以用动态规划算法求解,状态可以定义为在两个字符串的某段位置上的最长公共子序列的长度,状态转移方程比较简单。
2. 背包问题:有一个容量为 V 的背包和 n 种物品,每种物品的重量为 wi,价值为 vi,现在要用这些物品装满背包,使得背包中所装物品的总价值最大。
这个问题可以用动态规划算法求解,状态可以定义为在前 i 件物品中,体积为 j 的情况下能获得的最大价值,状态转移方程也比较简单。
3. 最短路问题:给定一个带权图,求出其中从起点到终点的最短路径。
这个问题可以用动态规划算法求解,状态可以定义为从起点到某个点的最短路径,状态转移方程可以通过分阶段来进行求解。
4. 求解最大子段和问题:给定一个序列,求出其中连续子段的和的最大值。
这个问题也可以用动态规划算法求解,状态可以定义为以某个位置为结尾的最大子段和,状态转移方程与之前的问题类似。
动态规划算法虽然能够解决很多问题,但是它也存在一些限制。
动态规划算法的计算复杂度较高,需要占用大量的内存空间。
动态规划算法的详细原理及使用案例一、引言动态规划是一种求解最优化问题的算法,它具有广泛的应用领域,如机器学习、图像处理、自然语言处理等。
本文将详细介绍动态规划算法的原理,并提供一些使用案例,以帮助读者理解和应用这一算法的具体过程。
二、动态规划的基本原理动态规划算法通过将问题分解为多个子问题,并利用已解决子问题的解来求解更大规模的问题。
其核心思想是利用存储技术来避免重复计算,从而大大提高计算效率。
具体来说,动态规划算法通常包含以下步骤:1. 定义子问题:将原问题分解为若干个子问题,这些子问题具有相同的结构,但规模更小。
这种分解可以通过递归的方式进行。
2. 定义状态:确定每个子问题的独立变量,即问题的状态。
状态具有明确的定义和可计算的表达式。
3. 确定状态转移方程:根据子问题之间的关系,建立状态之间的转移方程。
这个方程可以是简单的递推关系式、递归方程或其他形式的方程。
4. 解决问题:使用递推或其他方法,根据状态转移方程求解每个子问题,直到获得最终解。
三、动态规划的使用案例1. 背包问题背包问题是动态规划算法的经典案例之一。
假设有一个背包,它能容纳一定重量的物品,每个物品有对应的价值。
目的是在不超过背包总重量的前提下,选取最有价值的物品装入背包。
这个问题可以通过动态规划算法来求解。
具体步骤如下:(1)定义问题:在不超过背包容量的限制下,选取物品使得总价值最大化。
(2)定义状态:令dp[i][j]表示将前i个物品放入容量为j的背包中所能获得的最大价值。
(3)状态转移方程:dp[i][j] = max(dp[i-1][j-w[i]]+v[i], dp[i-1][j]),其中w[i]为第i个物品的重量,v[i]为第i个物品的价值。
(4)解决问题:根据状态转移方程依次计算每个子问题的解,并记录最优解,直到获得最终答案。
2. 最长公共子序列最长公共子序列(Longest Common Subsequence,简称LCS)是一种经典的动态规划问题,它用于确定两个字符串中最长的共同子序列。
动态规划——DP算法(DynamicPrograming)⼀、斐波那契数列(递归VS动态规划)1、斐波那契数列——递归实现(python语⾔)——⾃顶向下递归调⽤是⾮常耗费内存的,程序虽然简洁可是算法复杂度为O(2^n),当n很⼤时,程序运⾏很慢,甚⾄内存爆满。
1def fib(n):2#终⽌条件,也就是递归出⼝3if n == 0 or n == 1:4return 15else:6#递归条件7return (fib(n-1) + fib(n - 2))2、斐波那契数列——动态规划实现(python语⾔)——⾃底向上动态规划——将需要重复计算的问题保存起来,不需要下次重新计算。
对于斐波那契数列,算法复杂度为O(n)。
1def dp_fib(n):2#初始化⼀个数组,⽤于存储记录计算的结果。
3 res = [None] * (n + 1)4#前两项设置为1。
5 res[0] = res[1] = 16#⾃底向上,将计算结果存⼊数组内。
7for i in range(2, (n + 1)):8 res[i] = res[i-1] + res[i-2]9return res[n]3、⽅法概要 (1)构造⼀个公式,它表⽰⼀个问题的解是与它的⼦问题的解相关的公式: (2)为这些⼦问题做索引,以便于它们能够在表中更好的存储与检索(⽤数组存储)。
(3)以⾃底向上的⽅法来填写这个表格;⾸先填写最⼩的⼦问题的解。
(4)这就保证了当我们解决⼀个特殊的⼦问题时,可以利⽤⽐它更⼩的所有可利⽤的⼦问题的解。
总之,因为在上世纪40年代(计算机普及很少时),这些规划设计是与“列表”⽅法相关的,因此被称为动态规划——Dynamic Programing。
⼆、动态规划算法——思想简介1、DP算法思想 (1)将待求解的问题分解称若⼲个⼦问题,并存储⼦问题的解⽽避免计算重复的⼦问题,并由⼦问题的解得到原问题的解。
(2)动态规划算法通常⽤于求解具有某种最有性质的问题。
数据结构和算法-五⼤常⽤算法:动态规划算法参考:动态规划⼊门篇学习动态规划,愚认为,就是解决以下的三个问题:什么是动态规划?什么时候要⽤动态规划?怎么使⽤动态规划?让我们⼀个⼀个来解决!1、什么是动态规划?这⾥参考百度百科,动态规划是求解决策过程最优化的数学⽅法。
把多阶段过程转化为⼀系列单阶段问题,利⽤各阶段之间的关系,逐个求解,创⽴了解决这类过程优化问题的新⽅法——动态规划。
2、什么时候要⽤动态规划?如果要求⼀个问题的最优解(通常是最⼤值或者最⼩值),⽽且该问题能够分解成若⼲个⼦问题,并且⼩问题之间也存在重叠的⼦问题,则考虑采⽤动态规划。
3、怎么使⽤动态规划?我把下⾯称为动态规划五部曲:1. 判题题意是否为找出⼀个问题的最优解2. 从上往下分析问题,⼤问题可以分解为⼦问题,⼦问题中还有更⼩的⼦问题3. 从下往上分析问题,找出这些问题之间的关联(状态转移⽅程)4. 讨论底层的边界问题5. 解决问题(通常使⽤数组进⾏迭代求出最优解)纸上得来终觉浅,绝知此事要躬⾏。
举⼏个例⼦:例⼦1:剑指Offer(第⼆版)⾯试题14:剪绳⼦给你⼀根长度为n的绳⼦,请把绳⼦剪成m段 (m和n都是整数,n>1并且m>1)每段绳⼦的长度记为k[0],k[1],…,k[m].请问k[0]k[1]…*k[m]可能的最⼤乘积是多少?例如,当绳⼦的长度为8时,我们把它剪成长度分别为2,3,3的三段,此时得到的最⼤乘积是18.看完题⽬,我们按照上⾯提到的“动态规划五部”解决问题1、判题题意是否为找出⼀个问题的最优解看到字眼是“可能的最⼤乘积是多少”,判断是求最优解问题,可以⽤动态规划解决;2、从上往下分析问题,⼤问题可以分解为⼦问题,⼦问题中还有更⼩的⼦问题题⽬中举了个例⼦:当绳⼦的长度为8时,我们把它剪成长度分别为2,3,3的三段,此时得到的最⼤乘积是18;我们可以从这⾥开始突破,把长度为8绳⼦的最⼤乘积分解为数个⼦问题,长度为8我们可以把它看成长度为1和7的绳⼦的和,或者长度为2和6的绳⼦的和,或者长度为3和5的绳⼦的和and so on!到这⾥,相信⼤家已经看到⼀丝真理了吧?3. 从下往上分析问题,找出这些问题之间的关联(状态转移⽅程)在第⼆点时,我们已经从上到下分析问题了,现在我们要从下往上分析问题了。
动态规划算法详解及经典例题动态规划什么是动态规划?动态规划的⼤致思路是把⼀个复杂的问题转化成⼀个分阶段逐步递推的过程,从简单的初始状态⼀步⼀步递推,最终得到复杂问题的最优解。
基本思想与策略编辑:由于动态规划解决的问题多数有重叠⼦问题这个特点,为减少重复计算,对每⼀个⼦问题只解⼀次,将其不同阶段的不同状态保存在⼀个⼆维数组中。
1. 拆分问题:根据问题的可能性把问题划分成通过递推或者递归⼀步⼀步实现。
关键就是这个步骤,动态规划有⼀类问题就是从后往前推到,有时候我们很容易知道 : 如果只有⼀种情况时,最佳的选择应该怎么做.然后根据这个最佳选择往前⼀步推导,得到前⼀步的最佳选择 2. 定义问题状态和状态之间的关系:⽤⼀种量化的形式表现出来,类似于⾼中学的推导公式,因为这种式⼦很容易⽤程序写出来,也可以说对程序⽐较亲和(也就是最后所说的状态转移⽅程式) 3. 动态规划算法的基本思想与分治法类似,也是将待求解的问题分解为若⼲个⼦问题(阶段),按顺序求解⼦阶段,前⼀⼦问题的解,为后⼀⼦问题的求解提供了有⽤的信息。
在求解任⼀⼦问题时,列出各种可能的局部解,通过决策保留那些有可能达到最优的局部解,丢弃其他局部解。
依次解决各⼦问题,最后⼀个⼦问题就是初始问题的解。
我的理解是:⽐如我们找到最优解,我们应该讲最优解保存下来,为了往前推导时能够使⽤前⼀步的最优解,在这个过程中难免有⼀些相⽐于最优解差的解,此时我们应该放弃,只保存最优解,这样我们每⼀次都把最优解保存了下来,⼤⼤降低了时间复杂度。
动态规划解决问题的过程分为两步:1.寻找状态转移⽅程式2.利⽤状态转移⽅程式⾃底向上求解问题动态规划原理使⽤条件:可分为多个相关⼦问题,⼦问题的解被重复使⽤使⽤条件:可分为多个相关⼦问题,⼦问题的解被重复使⽤Optimal substructure(优化⼦结构):⼀个问题的优化解包含了⼦问题的优化解缩⼩⼦问题集合,只需那些优化问题中包含的⼦问题,降低实现复杂性我们可以⾃下⽽上的Subteties(重叠⼦问题):在问题的求解过程中,很多⼦问题的解将被多次使⽤。
动态规划算法详解及应用实例动态规划算法是一种常见的解决各种最优化问题的算法。
它适用于很多复杂的问题,如图形分析、路线规划、搜索引擎等等。
本文将详细讲解动态规划算法的基本原理、特点和应用实例,供大家学习和借鉴。
一、动态规划算法基本原理动态规划,简称DP,是一种递推式算法,通过将问题分解成一系列子问题,并按照一定的顺序对子问题进行求解,最终得到问题的最优解。
其主要思想是:当我们在解题时遇到一个问题时,如果能将这个问题划分成若干个与原问题相似但规模更小的子问题,而这些子问题又可以逐一求解,最终将所有子问题的结果汇总起来得到原问题的解,那么这个问题就可以使用动态规划算法解决。
由于动态规划算法中有“最优解”的要求,所以在求解过程中需要涉及到状态转移方程的设计。
状态转移方程是一个数学公式,它描述了一个状态如何从前一个状态转移而来,以及在当前状态下所做的某些决策对下一个状态的影响。
通过不断迭代求解状态转移方程,我们可以得到最优解。
二、动态规划算法的特点1、动态规划是一种自底向上的策略,通常需要维护一个状态表格,记录下每个阶段的最优解,最后汇总起来得到问题的最终解。
2、动态规划通常具有“无后效性”的特点,即求解某个决策问题时,当前状态之后的决策不会影响之前的决策。
因此,在涉及到状态转移时,只需考虑当前状态和以前的状态即可。
3、动态规划通常包含两个要素:最优子结构和重叠子问题。
最优子结构是指一个问题的最优解由其子问题的最优解递推而来,而重叠子问题则是指在递归求解的过程中,同一问题会被反复求解多次,因此需要使用记忆化搜索等技巧,避免重复计算。
4、动态规划算法的时间复杂度通常是O(n^2)或O(n^3),空间复杂度通常也会比较高。
三、应用实例:0-1背包问题0-1背包问题是指在背包容量固定的情况下,如何选择物品才能使得背包装载的价值最大,其中每个物品只能选择一次。
对于此类问题,可以采用动态规划算法进行求解。
首先需要确定问题的状态转移方程,具体如下:设f(i,j)表示在前i个物品中,当背包的容量为j时,能够装载的最大价值,那么状态转移方程为:f(i,j)=max{f(i-1,j), f(i-1,j-wi)+vi}其中,wi表示第i个物品的重量,vi表示第i个物品的价值。
动态规划算法的表达技巧分析
[摘要]动态规划算法是计算机领域中一种非常有效的算法,而对于很多要掌握它的学生而言,往往会表现出很大的困惑性,本文介绍了动态规划算法的三种表达技巧,并详细阐述了较难掌握的部分存储的动态规划算法。
[关键词]子问题辅助存储单元全部存储部分存储
一、引言
动态规划算法是一种以空间换取时间的算法,这里的空间即指计算过程中所用到的辅助存储单元。
经过我们多次分析发现,动态规划算法通常有三种表示形式,并且它们具有相同的时间复杂度和可供选择的空间复杂度。
选择不同的空间复杂度,即选择了不同数量级的内存分配。
对于一个算法而言,通常考虑较多是的其时间复杂度,而动态规划算法,其空间复杂度却非常值得深入考虑,能否减少辅助存储单元,并在它的前提下尽可能让算法时间复杂度得到优化,是一个值得分析的问题。
本论文首先介绍动态规划算法的三种写法,然后对其中的一种写法进行深入分析,介绍如何减小空间复杂度的方法,并结合acm 竞赛问题,介绍了一种利用指针技巧减少运算量的方法,使得算法在减少内存的同时仅仅增加了微不足道的运算量。
二、动态规划算法的三种常用写法
能够运用动态规划算法求解的问题都可以用一个递推关系式来
表达,此递推关系的基本原理即是:将所要求解的原始问题分解成规模较小的子问题来做,而子问题可以依据同样的分解原理分解成更小的子问题,直到不能分解为止,不能继续分解的问题称为最小规模的子问题,它的结果是已知的,按照分解的逆过程,由小规模子问题的结果可以推算出较大规模子问题的结果,直至最终推算出原始问题的结果。
这个递推关系式其实就是一种递归描述,并且递归描述中用来表示子问题的部分必然是是重复出现的。
动态规划算法的本质就是用一些辅助存储单元将这些重复出现的子问题结果保存起来,在每一个子问题第一次被计算出来后存储在其相应单元中,如果在后续计算过程中,一个已经有结果的子问题被要求再次计算时,动态规划算法就直接取用已经存储的结果,而不是像原始递归一样去重复计算,这样就保证了一个子问题只计算一次,从而大大减少了运算量。
对于一个只有n个子问题的原始问题,原始递归往往会付出指数数量级的运算代价,而动态规划只需要付出θ(n)的运算代价(假设算出一个子问题结果需要时间为θ(1))。
例如,斐波那契数列的递归描述为:
其中n为正整数,f(n)表示数列中第n项的值,为方便算法表达,假定n<100,不考虑整数溢出问题,其递归和三种动态规划算法分别如下。
递归写法的时间复杂度为θ( ),空间复杂度为θ(n),动态规划的三种写法时间复杂度均为θ(n),其中写法一与写法二的空间复杂度是θ(n),而写法三的空间复杂度是θ(1),它比前两种写法的空间复杂度要低一个数量级。
换一个角度讲,写法一与写法二是存储所有子问题的动态规划算法,而写法三是存储部分子问题的动态规划算法。
在运用动态规划算法解决问题时,辅助存储单元的数量级可以根据具体情况进行选择。
如果所有子问题的数量级太大而超出存储要求,可以考虑低一个数量级存储的动态规划算法,虽然这种写法可以计算出某一个目标值,但是由于它所对应的存储单元被反复改写,保留的信息是有限的。
例如,上面动态规划算法写法一和写法二,不仅求解保存了第n项的值,其前n-1项的值都被计算保存了,而写法三虽然计算出了第n项的值和前面相邻第n-1项的值,但是前n-2项的值已经无法获取,因此,这种节省内存的算法只适合于求解目标值较少的问题。
三、时间、空间复杂度的分析及改进策略
动态规划算法的时间、空间复杂度主要取决于子问题的个数,子问题个数由递推关系式中反映问题规模的参变量决定,其时间复
杂度与子问题个数成正比,而空间复杂度取决于对子问题结果的存储方式。
一般而言,对子问题结果进行全部存储,是最为常用的一种策略,可以在此基础上提出一种改进策略为部分存储。
有些问题需要存储全部的子问题,才能完成目标计算任务,而有些问题可以只用存储部分子问题,即可完成目标计算任务。
1.时间复杂度分析
动态规划算法的时间复杂度(记为t(n))取决于所有子问题个数的数量级(记为c(n))和每一个子问题被计算出需要付出的时间代价(记为x(n)),那么以上三种写法的动态规划算法的时间复杂度相同,并且均为公式一。
t(n)=c(n)*x(n) (公式一)
2.空间复杂度分析
类似于时间复杂度分析,动态规划算法的空间复杂度(记为
g(n))取决于所有子问题个数的数量级(记为c(n))和存储一个子问题所用的存储代价(记为g(n)),那么存储所有子问题的动态规划算法空间复杂度为公式二。
g(n)=c(n)*g(n) (公式二)
3.改进策略
对上面空间复杂度的一种改进策略是,采用部分存储的方式,将空间复杂度中的c(n)降低到c’(n),其对应计算式为公式三。
g(n)=c’(n)*g(n) (公式三)
例如一个子问题个数为n2的问题,存储所有子问题时,通常可以用n×n大小的二维数组,其空间复杂度为θ(n2);存储部分子问题时,可以只存储该n×n方阵中的一行(列)或几行(列),其空间复杂度为θ(n)。
前面介绍的费波那契数列的动态规划算法写法三,即是将空间复杂度由θ(n)降低到θ(1)之后的一种结果。
部分存储的改进策略,一般可以将空间复杂度降低一维,尤其是当n较大时,这种降维的效果会非常明显。
空间复杂度降低一维之后的动态规划写法,可以模仿前面的写法三。
四、改进策略的应用
一般情况下,存储部分子问题比存储所有子问题的动态规划算法要付出更多的运算量,甚至可能会高出一个数量级,但是,可以结合一些特殊技巧改进运算量,使其空间复杂度保持在公式一的情况。
下面结合北大acm网站上的一道题目1159,介绍动态规划算法改进策略的运用。
求往一个字符串中最少插入多少个字符可以使其变成回文字符串[1]。
分析过程如下,假设字符串为a[1]~a[n],其长度为n,用f(i,j)表示a[i]~a[j]部分要变成回文需要插入的最少字符个数,其中1≤i≤j≤n,f(1,n)即为原始问题,则有如下递归描述:
由递推式可知,对子问题全部存储的空间复杂度是θ(n2),其对应f(i,j)在存储矩阵中的逻辑关系如下图。
根据动态规划算法思想和递推式分析,在计算f(i,j)之前,必须保证f(i,j-1),f(i+1,j-1),f(i+1,j)已经被计算出并被保存,因此对于此矩阵中上三角部分的求解,其计算顺序可以采用从下往上逐行计算,每一行元素计算时从左往右进行的方式;另外,部分存储策略中最节省存储单元的方法是,只保存在计算顺序上从
f(i+1,j-1)到f(i,j)范围内的子问题结果,其算法是每计算出一个f(i,j)准备计算下一项f(i,j+1)时,将f(i+1,j-1)到f(i,j)范围对应的存储单元循环迭代,从而运用同样的循环语句完成整个计算过程,最后计算的一项即是右上角的原始问题f(1,n)。
为了避免这里面的循环迭代计算,可以采用与最节省存储单元等数量级的另外一种部分存储策略,即存储图中从f(i+1,j-1)到
f(i,j)范围内所有行的子问题结果,算法中用p1,p分别表示这两行,显然,p行中子问题f(i,j)对应的元素为p[j],其它子问题对应元素同理。
当p行计算结束后,按子问题计算顺序算法会接着计算p行的上面一行,我们对p,p1采用指针的技巧,当p行计算结束后,交换一下p与p1所指向的存储单元即可,从而减少对存储单元反复迭代的计算部分,其算法实现如下面代码所示。
五、结束语
本文对动态规划算法的空间复杂度提出了一种改进思路,并结合程序设计竞赛问题阐述了这种策略的详细设计过程,与广大师生,尤其是参加程序设计竞赛的学生,共同探讨了动态规划算法的表达技巧和改进技巧。
[参考文献]
[1] /problem?id=1159
(作者单位:1.湖北经济学院信息管理学院,2.武汉大学珞珈学院)。