动态规划问题 - 经典模型的状态转移方程

状态转移方程

动态规划中当前的状态每每依赖于前一阶段的状态和前一阶段的决策结果。例如咱们知道了第i个阶段的状态Si以及决策Ui,那么第i+1阶段的状态Si+1也就肯定了。因此解决动态规划问题的关键就是肯定状态转移方程,一旦状态转移方程肯定了,那么咱们就能够根据方程式进行编码。html

在前面的文章《动态规划-开篇》讲到了如何设计一个动态规划算法,有如下四个步骤:算法

一、刻画一个最优解的结构特征。数组

二、递归地定义最优解的值。ide

三、计算最优解的值,一般采用自底向上的方法。post

四、利用计算出的信息构造一个最优解。学习

对于肯定状态转移方程就在第一步和第二步中,首先要肯定问题的决策对象,接着对决策对象划分阶段并肯定各个阶段的状态变量,最后创建各阶段的状态变量的转移方程。优化

例如用dp[i]表示以序列中第i个数字结尾的最长递增子序列长度和最长公共子序列中用dp[i][j]表示的两个字符串中前 i、 j 个字符的最长公共子序列,咱们就是经过对这两个数字量的不断求解最终获得答案的。这个数字量就被咱们称为状态。状态是描述问题当前情况的一个数字量。首先,它是数字的,是能够被抽象出来保存在内存中的。其次,它能够彻底的表示一个状态的特征,而不须要其余任何的辅助信息。最后,也是状态最重要的特色,状态间的转移彻底依赖于各个状态自己,如最长递增子序列中,dp[x]的值由 dp[i](i < x)的值肯定。若咱们在分析动态规划问题的时候可以找到这样一个符合以上全部条件的状态,那么多半这个问题是能够被正确解出的。因此说,解动态规划问题的关键,就是寻找一个好的状态。编码

总结

下面对这几天的学习总结一下,将我遇到的各类模型的状态转移方程汇总以下:url

一、最长公共子串

假设两个字符串为str1和str2,它们的长度分别为n和m。d[i][j]表示str1中前i个字符与str2中前j个字符分别组成的两个前缀字符串的最长公共长度。这样就把长度为n的str1和长度为m的str2划分红长度为i和长度为j的子问题进行求解。状态转移方程以下:spa

  1. dp[0][j] = 0; (0<=j<=m)
  2. dp[i][0] = 0; (0<=i<=n)
  3. dp[i][j] = dp[i-1][j-1] +1; (str1[i] == str2[j])
  4. dp[i][j] = 0; (str1[i] != str2[j])

由于最长公共子串要求必须在原串中是连续的,因此一但某处出现不匹配的状况,此处的值就重置为0。

详细代码请看最长公共子串

二、最长公共子序列

区分一下,最长公共子序列不一样于最长公共子串,序列是保持子序列字符串的下标在str1和str2中的下标顺序是递增的,该字符串在原串中并不必定是连续的。一样的咱们能够假设dp[i][j]表示为字符串str1的前i个字符和字符串str2的前j个字符的最长公共子序列的长度。状态转移方程以下:

  1. dp[0][j] = 0; (0<=j<=m)
  2. dp[i][0] = 0; (0<=i<=n)
  3. dp[i][j] = dp[i-1][j-1] +1; (str1[i-1] == str2[j-1])
  4. dp[i][j] = max{dp[i][j-1],dp[i-1][j]}; (str1[i-1] != str2[j-1])

 详细代码请看最长公共子序列

三、最长递增子序列(最长递减子序列)

由于二者的思路都是同样的,因此只给出最长递增子序列的状态转移方程。假设有序列{a1,a2,...,an},咱们求其最长递增子序列长度。按照递推求解的思想,咱们用F[i]表明若递增子序列以ai结束时它的最长长度。当 i 较小,咱们容易直接得出其值,如 F[1] = 1。那么,如何由已经求得的 F[i]值推得后面的值呢?假设,F[1]到F[x-1]的值都已经肯定,注意到,以ax 结尾的递增子序列,除了长度为1的状况,其它状况中,ax都是紧跟在一个由 ai(i < x)组成递增子序列以后。要求以ax结尾的最长递增子序列长度,咱们依次比较 ax 与其以前全部的 ai(i < x), 若ai小于 ax,则说明ax能够跟在以ai结尾的递增子序列以后,造成一个新的递 增子序列。又由于以ai结尾的递增子序列最长长度已经求得,那么在这种状况下,由以 ai 结尾的最长递增子序列再加上 ax 获得的新的序列,其长度也能够肯定,取全部这些长度的最大值,咱们即能获得 F[x]的值。特殊的,当没有ai(i < x)小 于ax, 那么以 ax 结尾的递增子序列最长长度为1。 即F[x] = max{1,F[i]+1|ai<ax && i<x}。

详细代码请看最长递增子序列

 四、最大子序列和的问题

假设有序列{a1,a2,...,an},求子序列的和最大问题,咱们用dp[i]表示以ai结尾的子序列的最大和。

dp[1] = a1; (a1>=0 && i == 1)

dp[i] = dp[i-1]+ai; (ai>=0 && i>=2)

dp[i] = 0; (dp[i-1] + ai <=0 && i>=2)

详细代码请看最大子序列的和

 五、数塔问题(动态搜索)

给定一个数组data[n][m]构成一个数塔求从最上面走到最低端通过的路径和最大。能够假设dp[i][j]表示走到第i行第j列位置处的最大值,那么能够推出状态转移方程:

dp[i][j] = max{dp[i-1][j-1],dp[i-1][j]} + data[i][j];

for(i=n-1;i>=1;i--){

     for(j=1;j<=i;j++){

         dp[i][j]=max{dp[i-1][j-1],dp[i-1][j]}+s[i][j]

     }

}
View Code

六、(01)背包问题

这是一个经典的动态规划问题,另外在贪心算法里也有背包问题,至于两者的区别在此就不作介绍了。

假设有N件物品和一个容量为V的背包。第i件物品的体积是v[i],价值是c[i],将哪些物品装入背包可以使价值总和最大?

每一种物品都有两种可能即放入背包或者不放入背包。能够用dp[i][j]表示第i件物品放入容量为j的背包所得的最大价值,则状态转移方程能够推出以下:

dp[i][j]=max{dp[i-1][j-v[i]]+c[i],dp[i-1][j]};

  for (int i = 1;i <= N;i++) //枚举物品  
    {  
        for (int j = 0;j <= V;j++) //枚举背包容量  
        {  
            f[i][j] = f[i - 1][j];  
            if (j >= v[i])  
            {  
                f[i][j] = Max(f[i - 1][j],f[i - 1][j - v[i]] + c[i]);  
            }  
        }  
    }  
View Code

能够参照动态规划 - 0-1背包问题的算法优化动态规划-彻底背包问题动态规划-多重背包问题

七、矩阵连乘(矩阵链问题)-参考《算法导论》

例如矩阵链<A1,A2,A3>,它们的维数分别为10*100,100*5,5*50,那么若是顺序相乘即((A1A2)A3),共需10*100*5 + 10*5*50 = 7500次乘法,若是按照(A1(A2A3))顺序相乘,却需作100*5*50 + 10*100*50 = 75000次乘法。二者之间相差了10倍,因此说矩阵链的相乘顺序也决定了计算量的大小。

咱们用利用动态规划的方式(dp[i][j]表示第i个矩阵至第j个矩阵这段的最优解,还有对于两个矩阵A(i,j)*B(j,k)则须要i*j*k次乘法),推出状态转移方程:

dp[i][j] = 0; (i ==j,表示只有一个矩阵,计算次数为0)

dp[i][j] = min{dp[i][k] + dp[k+1][j] + p[i-1]*p[k]*p[j]}; (i<j && i<=k<j)            

dp[1][n]即为最终求解.

#define MAXSIZE 100

int dp[MAXSIZE][MAXSIZE];//存储最小的就算次数
int s[MAXSIZE][MAXSIZE];//存储断点,用在输出上面

int i, j, tmp;

for (int l = 2; l <= n; l++){//j-i的长度,因为长度为1是相同的矩阵那么为0不用计算
    for (i = 1; i <= n - l + 1; i++){//因为j-i =l - 1 , 那么j的最大值为n,因此i上限为 n - l+1;
        j = i + l - 1;//因为j-i = l - 1 , 那么j = l+i-1
        dp[i][j] = dp[i + 1][j] + r[i] * c[i] * c[j];//初始化,就是k = i;
        s[i][j] = i;
        for (k = i + 1; k < j; k++){//循环枚举k i < k < j
            tmp = dp[i][k] + dp[k + 1][j] + r[i] * c[k] * c[j];
            if (dp[i][j] > tmp){
                dp[i][j] = tmp;//更新为最小值
                s[i][j] = k;
            }
        }
    }
}

//递归调用输出
void output(int i, int j){
    if (i == j){
        printf("A%d", i);//当两个相等的时候就不用继续递归就输出A
        return;//返回上一层
    }

    else{
        printf("(");
        output(i, s[i][j]);
        printf(" x ");
        output(s[i][j] + 1, j);
        printf(")");
    }
}
View Code

ps

若有错误的地方或者本人理解错的地方,请指出,共同进步!!!

相关文章
相关标签/搜索