转【算法之动态规划(四)】动态规划笔试例题详解

 

终于来到了算法设计思想中最有趣的这部分,在去年的google笔试中,7道算法设计题有2道动态规划(Dynamic Programming)。

看了这么久的算法,这部分也是惟一感受到了比较难的地方,

从这篇文章开始,将花连续的篇幅来讨论一些对动态规划的认识和其中的问题。这包括一些例子:计算二项式系数,Warshall算法求传递闭包,Floyd算法求彻底最短路径,构造最

有二叉查找树,背包问题和记忆功能。也包括一些其余问题的解题报告(动态规划确实很难,对这一章的内容,我将搜索一些其余类型的问题来写解题报告,以真正的

理解动态规划),例如矩阵连乘,最长公共子列,等等。



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


1,什么是动态规划(DP)?


很是重要!,不要认为概念不重要,理解的深入,你才知道对于什么样的问题去考虑有没有动态规划的方法,以及如何去使用动态规划。


1)动态规划是运筹学中用于求解决策过程当中的最优化数学方法。 固然,咱们在这里关注的是做为一种算法设计技术,做为一种使用多阶段决策过程最优的通用方法。

它是应用数学中用于解决某类最优化问题的重要工具。


2)若是问题是由交叠的子问题所构成,咱们就能够用动态规划技术来解决它,通常来讲,这样的子问题出如今对给定问题求解的递推关系中,这个递推关系包含了相

同问题的更小子问题的解。动态规划法建议,与其对交叠子问题一次又一次的求解,不如把每一个较小子问题只求解一次并把结果记录在表中(动态规划也是空间换时间

的),这样就能够从表中获得原始问题的解。


关键词:

它每每是解决最优化问题滴

问题能够表现为多阶段决策(去网上查查什么是多阶段决策!)

交叠子问题:什么是交叠子问题,最有子结构性质。

动态规划的思想是什么:记忆,空间换时间,不重复求解,由交叠子问题从较小问题解逐步决策,构造较大问题的解。


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


关于斐波拉切数列能够做为最简单的一个例子来解释动态规划的思想,在前面讲斐波拉切数列时说过了,再也不叙述。


通常来讲,一个经典的动态规划算法时自底向上的(从较小问题的解,由交叠性质,逐步决策处较大问题的解),它须要解出给定问题的全部较小子问题。动态规划的

一个变种是试图避免对没必要要的子问题求解。若是采用自顶向下的递归来解,那么就避免了没必要要子问题的求解(相对于动态规划表现出优点),然而递归又会致使对

同一个子问题屡次求解(相对于动态规划表现出劣势),因此将递归和动态规划结合起来,就能够设计一种基于记忆功能的从顶向下的动态规划算法,在后面会讲。



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


计算二项式系数:

在排列组合里面,咱们有下面的式子(很容易用组合的定义来证实):



这个式子将C(n , k)的计算问题表述为了(问题描述)C(n-1 , k -1)和C(n -1, k)两个较小的交叠子问题。

初始条件:C(n , n) = C(n , 0) = 1

咱们能够用下列填矩阵的方式求出C(n , k):



该算法的时间复杂度是多少呢?能够大概的估计下,只填了下三角矩阵,为n*k/2  =  n*k,具体的次数为:



矩阵怎么填(填矩阵的顺序)?

按行来填矩阵:算法伪代码:



第1个for是控制行的,要填到第n行。第2个for来控制每行填到哪的,到i和k的较小值。从这2个for也能够看出复杂度是n*k。

实现:

html

复制代码
package Section8;


/*第八章 动态规划 计算二项式系数*/

publicclass BinoCoeff {

/**
* @param args
*/
publicstaticvoid main(String[] args) {
// TODO Auto-generated method stub
int result = Binomial(8,3);
System.out.println("输出8的二项式系数:");
for(int i =0;i <=8;i++)
System.out.println("C"+"("+8+","+ i +")"+" ———— "+ Binomial(8,i));
}

publicstaticint Binomial(int n,int k){
//计算二项式系数C(n,k)
int[][] result =newint[n+1][n+1];
for(int i =0;i <= n;i++) //按行来填矩阵
{
for(int j =0;j <= min(i,k);j++) // min(i,k)是这一行须要填的列数
{
//if(j == 0 || j == k)//书上写错了,他妈的
if(j ==0|| j == i)
result[i][j] =1;
else
result[i][j] = result[i -1][j -1] + result[i -1][j];
}
}

return result[n][k];
}

privatestaticint min(int i,int k){
if(i < k)
return i;
return k;
}

}
复制代码



结果:

输出8的二项式系数:
C(8,0) ———— 1
C(8,1) ———— 8
C(8,2) ———— 28
C(8,3) ———— 56
C(8,4) ———— 70
C(8,5) ———— 56
C(8,6) ———— 28
C(8,7) ———— 8
C(8,8) ———— 1

其实能够返回整个矩阵,这样就能够一次把全部的C(n , k)都计算出来。



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


再看动态规划:

上面棕色字体标出的就是一个动态规划算法的几个关键点:

1)怎么描述问题,要把问题描述为交叠的子问题

2)交叠子问题的初始条件(边界条件)

3)动态规划在形式上每每表现为填矩阵的形式(在后面会看到,有的能够优化空间复杂度,开一个数组便可,优化也是根据递推式的依赖形式的,后面有篇文章详细说明)

4)填矩阵的方式(或者说顺序)代表了什么?--它代表了这个动态规划从小到大产生的过程,专业点的说就是递推式的依赖形式决定了填矩阵的顺序




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




习题8.1   决定这一章的习题都认真的作一遍






1

a,相同点是动态规划和分治法都划分为了较小规模问题的解

b,不一样点是动态规划的较小子问题是交叠的,并且要存储较小子问题的解


2

a,参见代码,已实现

b,也能够按列来填矩阵(想一想为何?)---实际上这个问题就代表了再看动态规划第四点(填矩阵的方式代表了什么)


3

easy,在讲解中已多处指出


4

a, 空间效率也是nk,参见代码,或者从矩阵上也可看出

b,能够,这个问题的代表了再看动态规划第三点(优化空间复杂度)

---为何能够优化,上面说过,可不能够优化,以及如何优化空间复杂度依赖于它的递推形式:

---从填矩阵的那张图能够看出,这个动态规划产生各项的过程(若是按行填的话)是上一行的第 i-1 项和第 i 项加起来产生下一行的第 i 项,传统上,咱们从左往右填。

---事实上,根据它的产生过程(这个产生过程依赖于递推式自身的数学特征),能够从右往左填,这样开一个数组就行,在原数组上本地不动的填数,从右往左填可

以保证一个位置在覆盖之后不会再被用到(这是由递推式的属性决定的,须要画一画才看的比较清楚)。


这样开一个K大的数组就好了,具体的实现就不写了,已经分析的很清楚了,实现也不难



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


由以上分析,加习题,相信对于动态规划究竟是什么,核心思想,具体的操做细节,以及对于动态规划的理解都加深了吧,

有2点我以为很是重要,一是填矩阵的顺序,二是动态规划空间复杂度的优化:这2点都跟递推式的依赖关系有关(这是本质),在形式上就表现为填矩阵的时候你的

顺序要确保每填一个新位置时你所用到的那些位置(即它依赖的)要已经填好了,在空间优化上表现为当一个位置在之后还有用的时候你不能覆盖它


这2条结论很是重要,是深入理解动态规划的一个重要阶梯,我也是很久慢慢悟出来的,固然,它们有更professional的表述,在下一篇文章里我会贴出。


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


再来看2道实践的习题吧,也比较简单:



9,靠,电子版的居然跟纸质版的不太同样,电子版书上没有这个题,截不了图,抄一下吧:


问题:

国际象棋中的车能够水平的或竖直的移动,一个车要从一个棋盘的一角移到对角线的另外一角,有多少种最短路径?

a,用动态规划算法求解

b,用初等排列组合知识求解


b)先说b吧,这是个很简单的高中排列组合题目了,假设棋盘大小是n*n的(囧,象棋棋盘多大这个得想一想才知道,就说n吧),答案是C(2n , n)


a)用a方法作下吧(主要是培养下怎么去创建动态规划的递推式)

问题是从(0,0)移动到(n,n)有多少种方法?(最短路,即横n竖n,不能回退)


设C[i , j]表示从(0,0)移动到(i ,j)的方法数(描述问题,怎么去刻画C[i , j]的含义,是动态规划的一个关键点):

那么怎么才能走到(i ,j)呢,它的上一步一定是(i-1 ,j)或者(i ,j-1)-------(分析动态规划问题的逆向思惟,很重要,后面要讲)

这样就将问题描述为了交叠子问题:

C[i , j]  =  C[i -1, j]   +  C[i , j-1]        ( C[i , j]的含义  )


咱们要求的是C[n , n]


初始条件:

C[0 , j]  =  j           j从0到n  

C[i , 0]   =  i           i从0到n

即第一行第一列肯定。


填矩阵的形式:能够按行也能够按列。


以上分析画个图很容易看出来。剩下的实现就很简单了。




10





第一问就是个几率题,听起来比较拗口,其实不难,属于高中几率水平:



有了递推式后,发现其实跟上一题彻底同样,就是递推式里多乘了个几率值,难怪电子版省略了一个题,剩下的略


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


很是重要:

解这两道题后,应该知道一个动态规划的设计过程是怎样的:

我的体会是动态规划的难点在于前期的设计:


a)怎么描述问题,使它能表述为一个动态规划问题(具有什么特征?最有子结构,多阶段决策,思考)

b)递推式的写出(逆向思惟去分析或正向思惟去递归),肯定你要求的是哪一个值

c)有了递推式能够画个矩阵的图(通常只从式子上不太容易看出来,固然,对于牛人来讲能够藐视),在图中关注如下两点:

初始条件

填矩阵的顺序(即怎么去写代码控制语句)


有了这些以后,其实动态规划的代码都很简单,它的难点在于问题的描述和解决阶段,而不在于写代码的阶段,剩下的写代码基本上就是照着公式填矩阵。



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


差点把一个重要的问题忘了:

咱们来看看象棋问题的动态规划描述,它为何能够描述为动态规划的?

关于能够描述为交叠子问题,上面分析过了,


咱们再说下最有子结构和多阶段决策

最优子结构:有准确的定义,能够参见一些资料,我本身描述下就是:在动态规划求解过程当中的,子问题产生的解对于子问题来讲也是一个最优解

多阶段决策:一步步的决策,无后效性,决策只依赖于当前状态,不依赖于以前的状态。


看看象棋问题的最优子结构性质:在到达终点以前的任意(i , j)点所走过的方法数都是最少的。

多阶段决策:每次决定往哪走只跟当前在哪有关,跟之前怎么走的无关。


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


总结:

动态规划的思想,理解深度,以上亮色字体标出部分!!!!


这篇信息量仍是很大的,要仔细理解,多看几遍。算法

来源:http://www.cnblogs.com/kkgreen/archive/2011/06/26/2090702.html数组

相关文章
相关标签/搜索