做动态规划题的步骤和例子(还在完善中)

来源:互联网 发布:sql查询每小时 编辑:程序博客网 时间:2024/05/21 23:41

动态规划:
拿LCS举例,设两个字符段为X,Y,当x[i]==y[j]时,  c[i,j]=k为LCS。

算法为:

LCS(x,y,i,j){if(x[i]==y[j])then c[i.j]=LCS(x,y,i-1,j-1)+1;else c[i,j]=max{LCS(x,y,i-1,j),LCS(x,y,i,j-1)};return c[i,j];}
此算法像树


结合备忘法理解LCS:

LCS(x,y,i,j){if(c[i,j]==null)then if(x[i]==y[j])then c[i.j]=LCS(x,y,i-1,j-1)+1;else c[i,j]=max{LCS(x,y,i-1,j),LCS(x,y,i,j-1)};return c[i,j];}
备忘法即为了节省时间,将相同的工作省略,用备忘记录结果

图示:

       A B C  B DA B
       0  0  0  0  0 0 0
B 0 0  1  1  1  1 1 1
D 0 0  1  1  1  2 2 2
C 0 0  1  2  2  2 2 2
A 0 1  1  2  2  2 3 3
B 0 1  2  2  3  3 3 4
A 0 1  2  2  3  3 4 4

根据备忘法公式得到的表格例子

第一行和第一列为初始值0,如按公式将绿色字体比较取 MAX ,得到蓝色字体结果

红色字体从左到右自上而下的顺序即为寻找LCS的顺序,最后结果为4,序列为BCBA,当然这个结果不唯一,一般题目都会给出要求限制。



以下是度娘上的:

1.将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。保存已解决的子问题的答案,在需要时再找出已求得的答案,这样就可以避免大量的重复计算,节省时间。我们可以用一个表来记录所有已解的子问题的答案。不管该子问题以后是否被用到,只要它被计算过,就将其结果填入表中。这就是动态规划法的基本思路。具体的动态规划算法多种多样,但它们具有相同的填表格式。


2.给定k阶段状态变量x(k)的值后,如果这一阶段的决策变量一经确定,第k+1阶段的状态变量x(k+1)也就完全确定,即x(k+1)的值随x(k)和第k阶段的决策u(k)的值变化而变化,那么可以把这一关系看成(x(k),u(k))与x(k+1)确定的对应关系,用x(k+1)=Tk(x(k),u(k))表示。这是从k阶段到k+1阶段的状态转移规律,称为状态转移方程



基本结构

多阶段决策问题中,各个阶段采取的决策,一般来说是与时间有关的,决策依赖于当前状态,又随即引起状态的转移,一个决策序列就是在变化的状态中产生出来的,故有“动态”的含义,称这种解决多阶段决策最优化问题的方法为动态规划方法。

基本模型

根据上例分析和动态规划的基本概念,可以得到动态规划的基本模型如下:
(1)确定问题的决策对象。 (2)对决策过程划分阶段。 (3)对各阶段确定状态变量。 (4)根据状态变量确定费用函数和目标函数。 (5)建立各阶段状态变量的转移过程,确定状态转移方程。
状态转移方程的一般形式:
一般形式: U:状态; X:策略
  顺推:f[Uk]=opt{f[Uk-1]+L[Uk-1,Xk-1]} 其中, L[Uk-1,Xk-1]: 状态Uk-1通过策略Xk-1到达状态Uk 的费用 初始f[U1];结果:f[Un]。
倒推:
  f[Uk]=opt{f[Uk+1]+L[Uk,Xk]}
  L[Uk,Xk]: 状态Uk通过策略Xk到达状态Uk+1 的费用
  初始f[Un];结果:f(U1)

适用条件

任何思想方法都有一定的局限性,超出了特定条件,它就失去了作用。同样,动态规划也并不是万能的。适用动态规划的问题必须满足最优化原理和无后效性。
1.最优化原理(最优子结构性质) 最优化原理可这样阐述:一个最优化策略具有这样的性质,不论过去状态和决策如何,对前面的决策所形成的状态而言,余下的诸决策必须构成最优策略。简而言之,一个最优化策略的子策略总是最优的。一个问题满足最优化原理又称其具有最优子结构性质。
2.无后效性将各阶段按照一定的次序排列好之后,对于某个给定的阶段状态,它以前各阶段的状态无法直接影响它未来的决策,而只能通过当前的这个状态。换句话说,每个状态都是过去历史的一个完整总结。这就是无后向性,又称为无后效性。
3.子问题的重叠性 动态规划将原来具有指数级时间复杂度的搜索算法改进成了具有多项式时间复杂度的算法。其中的关键在于解决冗余,这是动态规划算法的根本目的。动态规划实质上是一种以空间换时间的技术,它在实现的过程中,不得不存储产生过程中的各种状态,所以它的空间复杂度要大于其它的算法。

作用

在编程中常用解决最长公共子序列问题、矩阵连乘问题、凸多边形最优三角剖分问题、电路布线等问题。

搜索

记忆化
给你一个数字三角形, 形式如下:
1
2 3
4 5 6
7 8 9 10
找出从第一层到最后一层的一条路,使得所经过的权值之和最小或者最大.
无论对于新手还是老手,这都是再熟悉不过的题了,很容易地,我们写出状态转移方程:
f[i][j]=a[i][j] + min{f[i+1][j],f[i+1][j+1]}(a[i][j]表示当前状态,f[i][j]表示指标函数)
对于动态规划算法解决这个问题,我们根据状态转移方程和状态转移方向,比较容易地写出动态规划的循环表示方法。但是,当状态和转移非常复杂的时候,也许写出循环式的动态规划就不是那么简单了。
解决方法:
我们尝试从正面的思路去分析问题,如上例,不难得出一个非常简单的递归函数:
int f(int i, int j, int (*a)[4]){    int f1, f2, tmp=0, k;    if(i==0||j==0)    return a[0][0];    if(j==i)    {        for(k=0;k<=i;k++)        tmp+=a[k][k];        return tmp;    }    f1=f(i-1, j, a);    f2=f(i-1, j-1, a);    if(f1<f2)        return f2+a[i][j];    else        return f1+a[i][j];}

显而易见,这个算法就是最简单的搜索算法。时间复杂度为2^n,明显是会超时的。分析一下搜索的过程,实际上,很多调用都是不必要的,也就是把产生过的最优状态,又产生了一次。为了避免浪费,很显然,我们存放一个opt数组:Opt[i, j] - 每产生一个f(i, j),将f(i, j)的值放入opt中,以后再次调用到f(i, j)的时候,直接从opt[i, j]来取就可以了。于是动态规划的状态转移方程被直观地表示出来了,这样节省了思维的难度,减少了编程的技巧,而运行时间只是相差常数的复杂度,避免了动态规划状态转移先后的问题,而且在相当多的情况下,递归算法能更好地避免浪费,在比赛中是非常实用的。
并且记忆搜索占的内存相对来说较少。
计算核心片段:
for(inti=n-1;i>=1;--i)//从倒数第二行开始{    for(intj=1;j<=i;j++)    {        if(a[i+1][j][1]>a[i+1][j+1][1])//左边大    {        a[i][j][2]=0;//选择左边        a[i][j][1]+=a[i+1][j][1];    }    else//右边大    {        a[i][j][2]=1;//选择右边        a[i][j][1]+=a[i+1][j+1][1];    }


0 0