动态规划

       动态规划是一种算法设计方法。

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

 

1、LCS问题

      很多人了解“动态规划”是从“算法导论”或是MIT的“算法导论”视频开始的,里面提到了一个非常经典的“最长公共子串LCS(Longest Common Sequence)”问题。

      问题大体是这样的:

      给出两个序列,求出他们的LCS,如:

                                                                        x[1,2……,m]=A B C B D A B;

                                                                        y[1,2,……,n]=B D C A B A;

      他们的LCS从我们的肉眼来看,大致有:BDAB BCAB BCBA三个,LCS的长度为4。

 

2、穷举法

      拿到这个问题,如果没有上面算法思想的话,最原始的会想到用“穷举算法(brute-force)”,我们先分析一下用“穷举算法”来解决这个问题的有关方面:

      解决方法是:拿着x序列的每一个子序列,去y序列中查看是否也有这个子序列。要找到x序列的子序列有2^m种,设这时我们拿到的一个 子序列BDA。现在我们拿着这轮循环的子序列BDA去和y序列比较,这个比较的算法复杂度我O(n),n为序列y的长度。有上面的步骤,我们成功得到了所有的x、y序列的子序列,那么,他们的LCS当然也就出来了。

      用穷举法解决LCS问题的复杂度为:O(n*2^m)。这个算法的复杂度是远远不能满足要求的。

      (当然,在实际编程时,我们一般用较短序列(这里的y)的所有子序列去和较长序列(这里的x)的子序列去比较,来得快一些。毕竟我们这里用的事“穷举法”,“穷举法”的关键在于尽量缩减“穷举”的范围。当可能“穷举”的范围非常大时,我们要谨慎使用。)

 

      既然上面提到了缩减“穷举”的范围,那么,在MIT的视频中提到了一种“缩减”的方法:既然我么要查找的是“LCS”,既然是最长的,那么,哪些明显比"LCS"短的部分就不需要比较,浪费CPU时间了。要想达”到比"LCS"短的部分就不需要比较“目标,可以这样做:先求出两个序列的LCS的长度,然后再求出某序列这个长度的所有子序列在一一比较,达到时间“穷举”范围的目的。      当然,我们咋“动态规划”中也可以用它来改进算法。

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

 

3、动态规划

      “动态规划DP(Dynamic Programming)”算法隆重登场啦!!!我们先了解一下什么是动态规划算法吧:

 

 动态规划的基本思想:

将问题分解为很多的子问题,通过对子问题的求解,来完成对大问题的求解。

关键在于,后面的问题可以利用前面解决的子问题的答案,达到减少计算量的目的。

 

例如:求解一个斐波那契数列,我们现在要求解这个数列中的某一位,有两种算法:

算法一:递归版本

F(n)
	if n=0 or n=1 then
		return 1
	else
		return F(n-1) + F(n-2)

算法二:动态规划版本

F(n)
	A[0] = A[1] ← 1
	for i ← 2 to n do
		A[i] ← A[i-1] + A[i-2]
	return A[n]

我们现在来比较一下这两个算法,看看他们的算法复杂度什么的:

乍一看上去,他们不就是一递归和非递归的关系么?如果你刚刚是一个变成新手,这样想无可厚非,但如果你现在有一定的编程年限的话,你应该一眼就看出这两个算法的不同:

实现来看一下递归版本的:

递归算法实在是太慢,不断地嵌套、嵌套。画出他的递归调用的图可以看出,它还进行了很多重复的计算,如:

计算第n位时,我们需要第n-1,n-2位,当然,我们就开始计算这两位的值。

计算第n-1位时,我们需要第n-2,n-3位,我们继续计算出来了。然后,下一步,我们有继续计算第n-2位,因为第n位需要它,全然不考虑我们在计算第n-1位时,已经算出了第n-2位。

随着这个n的不断加大,运算量将会以指数级增长,这时我么所绝对不愿意看到的。
再来看看“动态规划版本”:

我们将n前面的每一位的值都记录到一张表的相应位中,我们将它记录下来是因为我们之后的计算都需要用到前面那些位的结果。而我么仅仅需要付出一张表的内存空间而克服很多“递归版本”出现的问题。算法复杂度变成了O(N)。

 

好啦,至此,我们对“DP”已经有了一个感官上的了解。如果你的思绪还是纠结在“递归”的“非递归”实现上,你可以出去走走,透透气,顺便想想上面的那个例子,得到一个感官的认识就好了。一种思想的产生,首先需要一个萌芽。

然后,我们需要对我们的小灵感进行分析归纳,以期得到更普适的思维方法。

 

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

 

 

“动态规划”算法的算法思想

将待求解的问题分解成若干个子问题,并存储子问题的解而避免计算重复的子问题,并由子问题的解得到原问题的解。(这是一个很经典的句子,虽然不提倡死记硬背,但这个句子记住是非常有好处的)

 

“动态规划”算法的适用范围:

1、待求解问题具有“最优子结构”

也成为“最优化原理”。简而言之,一个最优化策略的子策略总是最优的。一个问题满足最优化原理又称其具有最优子结构性质

 

例如上图,若路线I和J是A到C的最优路径,则根据最优化原理,路线J必是从B到C的最优路线。这可用反证法证明:假设有另一路径J'是B到C的最优路径,则A到C的路线取I和J'比I和J更优,矛盾。从而证明J'必是B到C的最优路径。

最后,必须指出:最优化原理是动态规划的基础,任何问题,如果失去了最优化原理的支持,就不可能用动态规划方法计算。“最优化原理”对初次接触的人来说,在解决实际问题是通常会模棱两可(对我也是,ing,哼哼),需要反复揣摩,不要人云亦云。

2、“无后向性”

将各阶段按照一定的次序排列好之后,对于某个给定的阶段状态,它以前各阶段的状态无法直接影响它未来的决策,而只能通过当前的这个状态。换句话说,每个状态都是过去历史的一个完整总结。这就是无后向性,又称为无后效性

如果用前面的记号来描述无后向性,就是:对于确定的xk,无论p1,k-1如何,最优子策略pkn*是唯一确定的,这种性质称为无后向性。

(上面是我抄过来的,嘿嘿,还没学到、思考到能好好的总结出来)

3、“重叠子问题”

动态规划将原来具有指数级复杂度的搜索算法改进成了具有多项式时间的算法,其中的关键在于解决冗余,这是动态规划算法的根本目的。动态规划实质上是一种以空间换时间的技术,它在实现的过程中,不得不存储产生过程中的各种状态,所以它的空间复杂度要大于其它的算法。

设原问题的规模为n,容易看出,当子问题树中的子问题总数是n的超多项式函数,而不同的子问题数只是n的多项式函数时,动态规划法显得特别有意义,此时动态规划法具有线性时间复杂性。所以,能够用动态规划解决的问题还有一个显著特征:子问题的重叠性。这个性质并不是动态规划适用的必要条件,但是如果该性质无法满足,动态规划算法同其他算法相比就不具备优势。

 

能用“DP”解决问题的基本特征

1. 动态规划一般解决最值(最优,最大,最小,最长……)问题;

2. 动态规划解决的问题一般是离散的,可以分解(划分阶段)的;

3. 动态规划解决的问题必须包含最优子结构,即可以由(n1)的最优推导出n的最优

 

l动态规划算法的4个步骤

   1. 刻画最优解的结构特性. (一维,二维,三维数组)

   2. 递归的定义最优解. (状态转移方程)

   3. 以自底向上的方法来计算最优解.

   4. 从计算得到的解来构造一个最优解.

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

 

4、求解过程

好啦,对于“动态规划”的基本思想和适用条件的理解和掌握不是一朝一夕的问题,需要我们慢慢体会,下面开始用“动态规划”来解决LCS的问题:

 

对于求解LCS(x,y)这个问题,我们对他进行分解:

定义 c(i,j) 为LCS(x,y,i,j)(表示x[1,2,……i],y[1,2,……,j]两序列的LCS)的长度。我们的目标是求得LCS(x,y,m,n)。

我们可以思考一下,对于c(i,j)来说,它是不是满足“最优化原理”的,这个是首先要做的事情:

要求得c(i,j),很明显c(i-1,j),c(i,j-1),c(i-1,j-1)等子结构必须是最优的。符合“最优化原理”。

 

下面,我们得到了c(i,j)的递归结构:

c(i,j)=c(i-1,j-1),x[i]=y[j]时;

c(i,j)=max{c(i,j-1),c(i-1,j)},x[i]!=y[j]时;

 

我们就可以递归得到每一个c(i,j)。和斐波那契数列的“非递归”类似,我们需要将c(m,n)前面的每一个子问题的结果记录到一张表里面来避免重复计算。我们用一张二维表来存储。得到了这样一张表:

http://blog.chinaunix.net/attachment/201210/15/26548237_1350310972zRwl.jpg

通过对上面得到的图进行检索,可以得到所有的LCS。


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值