动态规划.docx

上传人:b****6 文档编号:4370601 上传时间:2022-12-01 格式:DOCX 页数:30 大小:38.42KB
下载 相关 举报
动态规划.docx_第1页
第1页 / 共30页
动态规划.docx_第2页
第2页 / 共30页
动态规划.docx_第3页
第3页 / 共30页
动态规划.docx_第4页
第4页 / 共30页
动态规划.docx_第5页
第5页 / 共30页
点击查看更多>>
下载资源
资源描述

动态规划.docx

《动态规划.docx》由会员分享,可在线阅读,更多相关《动态规划.docx(30页珍藏版)》请在冰豆网上搜索。

动态规划.docx

动态规划

五大常用算法之二:

动态规划算法

一、基本概念

动态规划过程是:

每次决策依赖于当前状态,又随即引起状态的转移。

一个决策序列就是在变化的状态中产生出来的,所以,这种多阶段最优化决策解决问题的过程就称为动态规划。

二、基本思想与策略

基本思想与分治法类似,也是将待求解的问题分解为若干个子问题(阶段),按顺序求解子阶段,前一子问题的解,为后一子问题的求解提供了有用的信息。

在求解任一子问题时,列出各种可能的局部解,通过决策保留那些有可能达到最优的局部解,丢弃其他局部解。

依次解决各子问题,最后一个子问题就是初始问题的解。

由于动态规划解决的问题多数有重叠子问题这个特点,为减少重复计算,对每一个子问题只解一次,将其不同阶段的不同状态保存在一个二维数组中。

与分治法最大的差别是:

适合于用动态规划法求解的问题,经分解后得到的子问题往往不是互相独立的(即下一个子阶段的求解是建立在上一个子阶段的解的基础上,进行进一步的求解)。

--------------------------------------------------------------------------------

三、适用的情况

能采用动态规划求解的问题的一般要具有3个性质:

(1)最优化原理:

如果问题的最优解所包含的子问题的解也是最优的,就称该问题具有最优子结构,即满足最优化原理。

(2)无后效性:

即某阶段状态一旦确定,就不受这个状态以后决策的影响。

也就是说,某状态以后的过程不会影响以前的状态,只与当前状态有关。

(3)有重叠子问题:

即子问题之间是不独立的,一个子问题在下一阶段决策中可能被多次使用到。

(该性质并不是动态规划适用的必要条件,但是如果没有这条性质,动态规划算法同其他算法相比就不具备优势)

--------------------------------------------------------------------------------

四、求解的基本步骤

动态规划所处理的问题是一个多阶段决策问题,一般由初始状态开始,通过对中间阶段决策的选择,达到结束状态。

这些决策形成了一个决策序列,同时确定了完成整个过程的一条活动路线(通常是求最优的活动路线)。

如图所示。

动态规划的设计都有着一定的模式,一般要经历以下几个步骤。

初始状态→│决策1│→│决策2│→…→│决策n│→结束状态

图1动态规划决策过程示意图

(1)划分阶段:

按照问题的时间或空间特征,把问题分为若干个阶段。

在划分阶段时,注意划分后的阶段一定要是有序的或者是可排序的,否则问题就无法求解。

(2)确定状态和状态变量:

将问题发展到各个阶段时所处于的各种客观情况用不同的状态表示出来。

当然,状态的选择要满足无后效性。

(3)确定决策并写出状态转移方程:

因为决策和状态转移有着天然的联系,状态转移就是根据上一阶段的状态和决策来导出本阶段的状态。

所以如果确定了决策,状态转移方程也就可写出。

但事实上常常是反过来做,根据相邻两个阶段的状态之间的关系来确定决策方法和状态转移方程。

(4)寻找边界条件:

给出的状态转移方程是一个递推式,需要一个递推的终止条件或边界条件。

一般,只要解决问题的阶段、状态和状态转移决策确定了,就可以写出状态转移方程(包括边界条件)。

实际应用中可以按以下几个简化的步骤进行设计:

(1)分析最优解的性质,并刻画其结构特征。

(2)递归的定义最优解。

(3)以自底向上或自顶向下的记忆化方式(备忘录法)计算出最优值

(4)根据计算最优值时得到的信息,构造问题的最优解

--------------------------------------------------------------------------------

五、算法实现的说明

动态规划的主要难点在于理论上的设计,也就是上面4个步骤的确定,一旦设计完成,实现部分就会非常简单。

使用动态规划求解问题,最重要的就是确定动态规划三要素:

(1)问题的阶段

(2)每个阶段的状态

(3)从前一个阶段转化到后一个阶段之间的递推关系。

递推关系必须是从次小的问题开始到较大的问题之间的转化,从这个角度来说,动态规划往往可以用递归程序来实现,不过因为递推可以充分利用前面保存的子问题的解来减少重复计算,所以对于大规模问题来说,有递归不可比拟的优势,这也是动态规划算法的核心之处。

确定了动态规划的这三要素,整个求解过程就可以用一个最优决策表来描述,最优决策表是一个二维表,其中行表示决策的阶段,列表示问题状态,表格需要填写的数据一般对应此问题的在某个阶段某个状态下的最优值(如最短路径,最长公共子序列,最大价值等),填表的过程就是根据递推关系,从1行1列开始,以行或者列优先的顺序,依次填写表格,最后根据整个表格的数据通过简单的取舍或者运算求得问题的最优解。

f(n,m)=max{f(n-1,m),f(n-1,m-w[n])+P(n,m)}

--------------------------------------------------------------------------------

六、动态规划算法基本框架

代码

1for(j=1;j<=m;j=j+1)//第一个阶段2xn[j]=初始值;34for(i=n-1;i>=1;i=i-1)/

/其他n-1个阶段5for(j=1;j>=f(i);j=j+1)//f(i)与i有关的表达式6xi[j]=j=max(或min){g(xi-1[j1:

j2]),......,g(xi-1[jk:

jk+1])};89t=g(x1[j1:

j2]);//由子问题的最优解求解整个问题的最优解的方案1011print(x1[j1]);1213for(i=2;i<=n-1;i=i+1)15{17t=t-xi-1[ji];1819for(j=1;j>=f(i);j=j+1)21if(t=xi[ji])23break;25}

动态规划

数学与计算机科学领域,动态规划用于解决那些可分解为重复子问题(overlappingsubproblems,想想递归求阶乘吧)并具有最优子结构(optimalsubstructure,想想最短路径算法)(如下所述)的问题,动态规划比通常算法花费更少时间。

上世纪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|BCD

2|

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)计算机国际

展开阅读全文
相关资源
猜你喜欢
相关搜索
资源标签

当前位置:首页 > 初中教育 > 中考

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1