上世纪40年代,RichardBellman最早使用动态规划这一概念表述通过遍历寻找最优决策解问题的求解过程。1953年,RichardBellman将动态规划赋予现代意义,该领域被IEEE纳入系统分析和工程中。为纪念Bellman的贡献,动态规划的核心方程被命名为贝尔曼方程,该方程以递归形式重申了一个优化问题。

在“动态规划”(dynamicprogramming)一词中,programming与“计算机编程”(computerprogramming)中的programming并无关联,而是来自“数学规划”(mathematicalprogramming),也称优化。因此,规划是指对生成活动的优化策略。举个例子,编制一场展览的日程可称为规划。在此意义上,规划意味着找到一个可行的活动计划。概述

图1使用最优子结构寻找最短路径:直线表示边,波状线表示两顶点间的最短路径(路径中其他节点未显示);粗线表示从起点到终点的最短路径。

不难看出,start到goal的最短路径由start的相邻节点到goal的最短路径及start到其相邻节点的成本决定。

最优子结构即可用来寻找整个问题最优解的子问题的最优解。举例来说,寻找图上某顶点到终点的最短路径,可先计算该顶点所有相邻顶点至终点的最短路径,然后以此来选择最佳整体路径,如图1所示。

一般而言,最优子结构通过如下三个步骤解决问题:

a)将问题分解成较小的子问题;

b)通过递归使用这三个步骤求出子问题的最优解;

c)使用这些最优解构造初始问题的最优解。

子问题的求解是通过不断划分为更小的子问题实现的,直至我们可以在常数时间内求解。

图2Fibonacci序列的子问题示意图:使用有向无环图(DAG,directedacyclicgraph)而非树表示重复子问题的分解。

为什么是DAG而不是树呢?答案就是,如果是树的话,会有很多重复计算,下面有相关的解释。

一个问题可划分为重复子问题是指通过相同的子问题可以解决不同的较大问题。例如,在Fibonacci序列中,F3=F1+F2和F4=F2+F3都包含计算F2。由于计算F5需要计算F3和F4,一个比较笨的计算F5的方法可能会重复计算F2两次甚至两次以上。这一点对所有重复子问题都适用:愚蠢的做法可能会为重复计算已经解决的最优子问题的解而浪费时间。

为避免重复计算,可将已经得到的子问题的解保存起来,当我们要解决相同的子问题时,重用即可。该方法即所谓的缓存(memoization,而不是存储memorization,虽然这个词亦适合,姑且这么叫吧,这个单词太难翻译了,简直就是可意会不可言传,其意义是没计算过则计算,计算过则保存)。当我们确信将不会再需要某一解时,可以将其抛弃,以节省空间。在某些情况下,我们甚至可以提前计算出那些将来会用到的子问题的解。

总括而言,动态规划利用:

1)重复子问题

2)最优子结构

3)缓存

动态规划通常采用以下两种方式中的一种两个办法:

自顶向下:将问题划分为若干子问题,求解这些子问题并保存结果以免重复计算。该方法将递归和缓存结合在一起。

自下而上:先行求解所有可能用到的子问题,然后用其构造更大问题的解。该方法在节省堆栈空间和减少函数调用数量上略有优势,但有时想找出给定问题的所有子问题并不那么直观。

为了提高按名传递(call-by-name,这一机制与按需传递call-by-need相关,复习一下参数传递的各种规则吧,简单说一下,按名传递允许改变实参值)的效率,一些编程语言将函数的返回值“自动”缓存在函数的特定参数集合中。一些语言将这一特性尽可能简化(如Scheme、CommonLisp和Perl),也有一些语言需要进行特殊扩展(如C++,C++中使用的是按值传递和按引用传递,因此C++中本无自动缓存机制,需自行实现,具体实现的一个例子是AutomatedMemoizationinC++)。无论如何,只有指称透明(referentiallytransparent,指称透明是指在程序中使用表达式、函数本身或以其值替换对程序结果没有任何影响)函数才具有这一特性。例子

1.Fibonacci序列

寻找Fibonacci序列中第n个数,基于其数学定义的直接实现:

functionfib(n)

ifn=0

return0

elseifn=1

return1

returnfib(n-1)+fib(n-2)

如果我们调用fib(5),将产生一棵对于同一值重复计算多次的调用树:fib(5)

fib(4)+fib(3)

(fib(3)+fib(2))+(fib(2)+fib(1))

((fib(2)+fib(1))+(fib(1)+fib(0)))+((fib(1)+fib(0))+fib(1))

(((fib(1)+fib(0))+fib(1))+(fib(1)+fib(0)))+((fib(1)+fib(0))+fib(1))

特别是,fib(2)计算了3次。在更大规模的例子中,还有更多fib的值被重复计算,将消耗指数级时间。

现在,假设我们有一个简单的映射(map)对象m,为每一个计算过的fib及其返回值建立映射,修改上面的函数fib,使用并不断更新m。新的函数将只需O(n)的时间,而非指数时间:

varm:=map(0→1,1→1)

functionfib(n)

ifmapmdoesnotcontainkeyn

m[n]:=fib(n-1)+fib(n-2)

returnm[n]

这一保存已计算出的数值的技术即被称为缓存,这儿使用的是自顶向下的方法:先将问题划分为若干子问题,然后计算和存储值。

在自下而上的方法中,我们先计算较小的fib,然后基于其计算更大的fib。这种方法也只花费线性(O(n))时间,因为它包含一个n-1次的循环。然而,这一方法只需要常数(O(1))的空间,相反,自顶向下的方法则需要O(n)的空间来储存映射关系。

functionfib(n)

varpreviousFib:=0,currentFib:=1

ifn=0

return0

elseifn=1

return1

repeatn-1times

varnewFib:=previousFib+currentFib

previousFib:=currentFib

currentFib:=newFib

returncurrentFib

在这两个例子,我们都只计算fib(2)一次,然后用它来计算fib(3)和fib(4),而不是每次都重新计算。

2.一种平衡的0-1矩阵

考虑n*n矩阵的赋值问题:只能赋0和1,n为偶数,使每一行和列均含n/2个0及n/2个1。例如,当n=4时,两种可能的方案是:

+----++----+

|0101||0011|

|1010||0011|

|0101||1100|

|1010||1100|

+----++----+

问:对于给定n,共有多少种不同的赋值方案。

至少有三种可能的算法来解决这一问题:穷举法(bruteforce)、回溯法(backtracking)及动态规划(dynamicprogramming)。穷举法列举所有赋值方案,并逐一找出满足平衡条件的方案。由于共有C(n,n/2)^n种方案(在一行中,含n/2个0及n/2个1的组合数为C(n,n/2),相当于从n个位置中选取n/2个位置置0,剩下的自然是1),当n=6时,穷举法就已经几乎不可行了。回溯法先将矩阵中部分元素置为0或1,然后检查每一行和列中未被赋值的元素并赋值,使其满足每一行和列中0和1的数量均为n/2。回溯法比穷举法更加巧妙一些,但仍需遍历所有解才能确定解的数目,可以看到,当n=8时,该题解的数目已经高达116963796250。动态规划则无需遍历所有解便可确定解的数目(意思是划分子问题后,可有效避免若干子问题的重复计算)。

通过动态规划求解该问题出乎意料的简单。考虑每一行恰含n/2个0和n/2个1的k*n(1<=k<=n)的子矩阵,函数f根据每一行的可能的赋值映射为一个向量,每个向量由n个整数对构成。向量每一列对应的一个整数对中的两个整数分别表示该列上该行以下已经放置的0和1的数量。该问题即转化为寻找f((n/2,n/2),(n/2,n/2),...,(n/2,n/2))(具有n个参数或者说是一个含n个元素的向量)的值。其子问题的构造过程如下:

1)最上面一行(第k行)具有C(n,n/2)种赋值;

2)根据最上面一行中每一列的赋值情况(为0或1),将其对应整数对中相应的元素值减1;

3)如果任一整数对中的任一元素为负,则该赋值非法,不能成为正确解;

4)否则,完成对k*n的子矩阵中最上面一行的赋值,取k=k-1,计算剩余的(k-1)*n的子矩阵的赋值;

5)基本情况是一个1*n的细小的子问题,此时,该子问题的解的数量为0或1,取决于其向量是否是n/2个(0,1)和n/2个(1,0)的排列。

例如,在上面给出的两种方案中,向量序列为:

((2,2)(2,2)(2,2)(2,2))((2,2)(2,2)(2,2)(2,2))k=4

01010011

((1,2)(2,1)(1,2)(2,1))((1,2)(1,2)(2,1)(2,1))k=3

10100011

((1,1)(1,1)(1,1)(1,1))((0,2)(0,2)(2,0)(2,0))k=2

01011100

((0,1)(1,0)(0,1)(1,0))((0,1)(0,1)(1,0)(1,0))k=1

10101100

((0,0)(0,0)(0,0)(0,0))((0,0)(0,0),(0,0)(0,0))

动态规划在此的意义在于避免了相同f的重复计算,更进一步的,上面着色的两个f,虽然对应向量不同,但f的值是相同的,想想为什么吧:D。

该问题解的数量(序列a058527在OEIS)是1,2,90,297200,116963796250,6736218287430460752,...

下面的外部链接中包含回溯法的Perl源代码实现,以及动态规划法的MAPLE和C语言的实现。

3.棋盘

考虑n*n的棋盘及成本函数C(i,j),该函数返回方格(i,j)相关的成本。以5*5的棋盘为例:

5|67478

4|76114

3|35782

2|26702

1|73561

-+-----

|12345

可以看到:C(1,3)=5

从棋盘的任一方格的第一阶(即行)开始,寻找到达最后一阶的最短路径(使所有经过的方格的成本之和最小),假定只允许向左对角、右对角或垂直移动一格。

5|

4|

3|

2|xxx

1|o

-+-----

|12345

该问题展示了最优子结构。即整个问题的全局解依赖于子问题的解。定义函数q(i,j),令:q(i,j)表示到达方格(i,j)的最低成本。

如果我们可以求出第n阶所有方格的q(i,j)值,取其最小值并逆向该路径即可得到最短路径。

记q(i,j)为方格(i,j)至其下三个方格((i-1,j-1)、(i-1,j)、(i-1,j+1))最低成本与c(i,j)之和,例如:

5|

4|A

3|BCD2|

1|

-+-----

|12345

q(A)=min(q(B),q(C),q(D))+c(A)

定义q(i,j)的一般形式:

|-inf.j<1orj>n

q(i,j)=-+-c(i,j)i=1

|-min(q(i-1,j-1),q(i-1,j),q(i-1,j+1))+c(i,j)otherwise.

方程的第一行是为了保证递归可以退出(处理边界时只需调用一次递归函数)。第二行是第一阶的取值,作为计算的起点。第三行的递归是算法的重要组成部分,与例子A、B、C、D类似。从该定义我们可以直接给出计算q(i,j)的简单的递归代码。在下面的伪代码中,n表示棋盘的维数,C(i,j)是成本函数,min()返回一组数的最小值:

functionminCost(i,j)

ifj<1orj>n

returninfinity

elseifi=1

returnc(i,j)

else

returnmin(minCost(i-1,j-1),minCost(i-1,j),minCost(i-1,j+1))+c(i,j)

需要指出的是,minCost只计算路径成本,并不是最终的实际路径,二者相去不远。与Fibonacci数相似,由于花费大量时间重复计算相同的最短路径,这一方式慢的恐怖。不过,如果采用自下而上法,使用二维数组q[i,j]代替函数minCost,将使计算过程快得多。我们为什么要这样做呢?选择保存值显然比使用函数重复计算相同路径要简单的多。

我们还需要知道实际路径。路径问题,我们可以通过另一个前任数组p[i,j]解决。这个数组用于描述路径,代码如下:

functioncomputeShortestPathArrays()

forxfrom1ton

q[1,x]:=c(1,x)

foryfrom1ton

q[y,0]:=infinity

q[y,n+1]:=infinity

foryfrom2ton

forxfrom1ton

m:=min(q[y-1,x-1],q[y-1,x],q[y-1,x+1])

q[y,x]:=m+c(y,x)

ifm=q[y-1,x-1]

p[y,x]:=-1

elseifm=q[y-1,x]

p[y,x]:=0

else

p[y,x]:=1

剩下的求最小值和输出就比较简单了:

functioncomputeShortestPath()

computeShortestPathArrays()

minIndex:=1

min:=q[n,1]

forifrom2ton

ifq[n,i]

minIndex:=i

min:=q[n,i]

printPath(n,minIndex)

functionprintPath(y,x)

print(x)

print("

ify=2

print(x+p[y,x])

else

printPath(y-1,x+p[y,x])

4.序列比对

序列比对是动态规划的一个重要应用。序列比对问题通常是使用编辑操作(替换、插入、删除一个要素等)进行序列转换。每次操作对应不同成本,目标是找到编辑序列的最低成本。

可以很自然地想到使用递归解决这个问题,序列A到B的最优编辑通过以下措施之一实现:

插入B的第一个字符,对A和B的剩余序列进行最优比对;

删去A的第一个字符,对A和B进行最优比对;

用B的第一个字符替换A的第一个字符,对A的剩余序列和B进行最优比对。

局部比对可在矩阵中列表表示,单元(i,j)表示A[1..i]到b[1..j]最优比对的成本。单元(i,j)的成本计算可通过累加相邻单元的操作成本并选择最优解实现。至于序列比对的不同实现算法,参见Smith-Waterman和Needleman-Wunsch。

对序列比对的话题并不熟悉,更多的话也无从谈起,有熟悉的朋友倒是可以介绍一下。应用动态规划的算法

1)许多字符串操作算法如最长公共子列、最长递增子列、最长公共字串;

2)将动态规划用于图的树分解,可以有效解决有界树宽图的生成树等许多与图相关的算法问题;

3)决定是否及如何可以通过某一特定上下文无关文法产生给定字符串的Cocke-Younger-Kasami(CYK)算法;

4)计算机国际象棋中转换表和驳斥表的使用;

5)Viterbi算法(用于隐式马尔可夫模型);

6)Earley算法(一类图表分析器);

7)Needleman-Wunsch及其他生物信息学中使用的算法,包括序列比对、结构比对、RNA结构预测;

8)Levenshtein距离(编辑距离);

9)弗洛伊德最短路径算法;

10)连锁矩阵乘法次序优化;

11)子集求和、背包问题和分治问题的伪多项式时间算法;

12)计算两个时间序列全局距离的动态时间规整算法;

13)关系型数据库的查询优化的Selinger(又名SystemR)算法;

14)评价B样条曲线的DeBoor算法;

15)用于解决板球运动中断问题的Duckworth-Lewis方法;

16)价值迭代法求解马尔可夫决策过程;

17)一些图形图像边缘以下的选择方法,如“磁铁”选择工具在Photoshop;

18)间隔调度;

19)自动换行;

20)巡回旅行商问题(又称邮差问题或货担郎问题);

21)分段最小二乘法;

22)音乐信息检索跟踪。

对于这些算法应用,大多未曾接触,甚至术语翻译的都有问题,鉴于本文主要在于介绍动态规划,所以仓促之中,未及查证。相关参考Adda,Jerome,andCooper,Russell,2003.

RichardBellman,1957,DynamicProgramming,PrincetonUniversityPress.Doverpaperbackedition(2003),ISBN0486428095.

Bertsekas,D.P.,2000.DynamicProgrammingandOptimalControl,Vols.1&2,2nded.AthenaScientific.ISBN1-886529-09-4.

Giegerich,R.,Meyer,C.,andSteffen,P.,2004,"ADisciplineofDynamicProgrammingoverSequenceData,"ScienceofComputerProgramming51:215-263.

NancyStokey,andRobertE.Lucas,withEdwardPrescott,1989.RecursiveMethodsinEconomicDynamics.HarvardUniv.Press.

S.P.Meyn,2007.ControlTechniquesforComplexNetworks,CambridgeUniversityPress,2007.外部链接Dyna,adeclarativeprogramminglanguagefordynamicprogrammingalgorithms

Wagner,DavidB.,1995,"DynamicProgramming."AnintroductoryarticleondynamicprogramminginMathematica.

MITcourseonalgorithms-IncludesavideolectureonDPalongwithlecturenotes--Seelecture15.

King,Ian,2002(1987),"ASimpleIntroductiontoDynamicProgramminginMacroeconomicModels."Anintroductiontodynamicprogrammingasanimportanttoolineconomictheory.

DynamicProgramming:fromnovicetoadvancedATopCoder.comarticlebyDumitruonDynamicProgramming

AlgebraicDynamicProgramming-aformalizedframeworkfordynamicprogramming,includinganentry-levelcoursetoDP,UniversityofBielefeld

java动态规划算阶乘_动态规划算法相关推荐

  1. Rosalind Java|Longest Increasing Subsequence动态规划算法

    Rosalind编程问题之计算集合中最长的递增元素子集. Longest Increasing Subsequence Problem: A subsequence of a permutation ...

  2. java sorted排序_【算法】排序算法之计数排序

    前几回,我们已经对冒泡排序.直接插入排序.希尔排序.选择排序.快速排序.归并排序.堆排序做了说明分析.本回,将对计数排序进行相关说明分析. 一.排序算法系列目录说明 冒泡排序(Bubble Sort) ...

  3. 快速排序 java导包_排序算法-快速排序(Java实现)

    上篇我们讲了冒泡排序,这次我们讲它的升级版快速排序,"快速",一看就是个好算法~ 快速排序(QuickSort)是啥? 我们先看下百度百科的介绍快速排序(Quicksort)是对冒 ...

  4. java多线程阶乘_【java学习记录】11.用多线程实现阶乘的计算过程和结果的读取(thread类ReadThread )...

    (源码在上传的压缩包"[java学习记录]11.多线程实现阶乘计算(Thread类和Runnable接口)"中可看到,该压缩包可下载)java * 采用Thread类的子类实现线程 ...

  5. java路由方法_路由选择算法的JAVA实现.doc

    路由选择算法的JAVA实现 路由选择算法的JAVA实现 第15卷第7期电脑开发与应用 路由选择算法的JAVA实现 JAVAImplementationofRouterSelectionAlgorith ...

  6. java 有序列表_关于算法:在Java中为列表列表生成唯一的有序非重复组合

    我知道有很多类似的问题,并且已经阅读了几个小时.但是它们似乎都不符合我的要求. 我有列表列表(list >),列表可以是任何大小. 例: 我的外部列表大小是:4 清单内容 1. list(0) ...

  7. java快速排序泛型_泛型算法_快速排序源码

    #include using namespace std;int a[1000000];void qsort(int l,int r){int x=a[l];int i=l;int j=r;if(l& ...

  8. java 比赛赛程_分治算法兵乓球比赛日程(java)

    分治算法之兵乓球比赛日程 分治算法的基本思想是将一个规模为N的问题分解为K个规模较小的子问题,这些子问题相互独立且与原问题性质相同.求出子问题的解,就可得到原问题的解.也就是字面上的解释是" ...

  9. java车间调度算法_混合算法(GA+TS)求解作业车间调度问题代码解读+完整JAVA代码...

    程序猿声 代码黑科技的分享区 前两篇文章中,我们介绍了FJSP问题,并梳理了一遍HA算法.这一篇文章对小编实现的(很乱很烂的)代码进行简单解读. 往期回顾: 代码下载请关注公众号,后台回复[FJSPH ...

最新文章

  1. SSL_TLS快速扫描器SSLScan常用命令集合大学霸IT达人
  2. USTC English Club Note20171014(2)
  3. boost::log::string_literal用法的测试程序
  4. Java DB中的Java存储过程
  5. mysql2005卸载步骤,二次安装mysql步骤
  6. sqlite3使用sqlite2创建的数据库
  7. 组合算法 C++高效实现 (二进制辅助法)
  8. mysql 收藏表如何建立_代码收藏系列--mysql创建数据库、数据表、函数、存储过程命令...
  9. mate20pro换鸿蒙系统,鸿蒙2.0下载
  10. 软件需求分析文档模板
  11. TwinCAT3 EL6070-1648授权模块使用
  12. React-pdf:pdf预览插件实践
  13. win10卓越性能模式
  14. xy坐标正负方向_xy坐标分别代表什么
  15. 华为云MVP周峥:气象预报是个技术活,大数据、超算、AI,缺一不可
  16. windows桌面远程连接(以XP连windows7为例)
  17. excel宏 java,Microsoft Excel宏运行Java程序
  18. 百度ueditor编辑器如何使用自定义的高大上高亮皮肤?
  19. 电缆 电流_如何不用电缆观看每项运动(运动返回时)
  20. 机器视觉2D点旋转中心标定及旋转后坐标计算

热门文章

  1. 虾皮如何注册店铺_虾皮跨境电商怎样注册店铺?做(shopee)虾皮电商靠谱吗
  2. 打开黑色_表哥出差带回来一箱苹果,打开后发现是黑色的,大家表示都没见过...
  3. oracle在数据表中获取年月日时分秒
  4. android apk反编译工具下载,Android apk反编译工具下载与使用
  5. 过拟合解决方法python_《python深度学习》笔记---4.4、过拟合与欠拟合(解决过拟合常见方法)...
  6. java加密字符串,可解密
  7. java 远程 shell脚本_Java 远程调用 shell脚本
  8. java 网络编程 聊天_Java——网络编程(实现基于命令行的多人聊天室)
  9. maven 多模块项目:单独构建某个模块
  10. spring mvc字符编码过滤器 CharacterEncodingFilter ,添加例外url