码迷,mamicode.com
首页 > 其他好文 > 详细

动态规划

时间:2015-08-12 14:42:57      阅读:225      评论:0      收藏:0      [点我收藏+]

标签:

动态规划的关键是找出一条关系表达式,然后根据该表达式循环或递归。往往需要把一些中间状态保存下来,避免重复计算。

数字三角形
有一个由正整数组成的三角形,第一行只有一个数,除了最下行之外 每个数的左下方和右下方各有一个数。
从第一行的数开始,每次可以往左下或右下走一格,直到走到三角形底端,把沿 途经过的数全部加起来作为得分。如何走,使得这个得分尽量大?如图:
技术分享
如果用贪心算法,路径为白色框,明显它的和小于灰色框的和。
可以用递归解决:
假设以某一点K为起点的最大路径和为maxK,那么maxK等于其左子节点maxKL加上K点的值或等于右子节点maxKRaft加上K点的值,具体要看哪个大,递归该过程。

最长公共子序列问题
给出两个序列x[1...m]和y[1...n],找出二者的一个最长公共 子序列。一个子序列是原序列删除一些元素后得到的,剩下的元素必须保持相对顺序,如图:
技术分享

解法:
从后往前递归
如果两序列的最后两个元素相等,则d[i,j] = d[i-1,j-1]+1,d[i-1,j-1]表示去掉最后一个元素的两个序列的最长公共子序列长度
如果最后两个元素不相等,则d[i,j] = max{d[i,j-1],d[i-1,j]},然后再递归找d[i,j-1]和d[i-1,j]
如果要获得公共子序列,可以在递归的时候返回当前经过的公共元素,也就是说,如果最后两元素相等则返回当前经过的元素加上该相等元素,如果元素不相等则返回当前已经过的元素,比较长度的时候可以通过判断经过元素的长度即可。

字符串相似度(编辑距离)

对于两个字符串A和B,通过基本的增删改将字符串A改成B,或者将B改成A,在改变的过程中我们使用的最少步骤称之为“编辑距离”。

比如如下的字符串:我们通过种种操作,编辑距离为3。

技术分享

与最长公共子序列类似,公式为:

现有两个序列X={x1,x2,x3,...xi},Y={y1,y2,y3,....,yi},

设一个C[i,j]保存Xi与Yj的当前最小的LD。

①:当 Xi = Yi 时,则C[i,j]=C[i-1,j-1];

②:当 Xi != Yi 时,则C[i,j]=Min{C[i-1,j-1],C[i-1,j],C[i,j-1]}+1;

最终我们的C[i,j]一直保存着最小的LD。

C[i-1,j-1]:表示修改
C[i-1,j]:表示插入
C[i,j-1]:表示删除
C实现:

#include<stdio.h>

int LD(char*A,char*B,inti,intj)

{

    if (i == -1)

    {

        if (j >= 0)

            returnj + 1;

        else

            return 0;

    }

    if (j == -1)

        returni + 1;

    if (A[i] == B[j])

        return LD(A, B, i - 1, j - 1);

    if (A[i] != B[j])

    {

        int modifyLD = LD(A,B,i - 1, j - 1);

        int insertLD = LD(A, B, i - 1, j);

        int deleteLD = LD(A, B, i, j - 1);

        if (modifyLD <= insertLD)

        {

            if (modifyLD <= deleteLD)

                return modifyLD+1;

            return deleteLD+1;

        }

        if (insertLD <= deleteLD)

            return insertLD+1;

        return deleteLD+1;

    }

}

void main()

{

    char A[50] = "fabcdxsxsss";

    char B[50] = "fbcdefdxsxscxc";

    printf("%d",LD(A,B,strlen(A),strlen(B)));

    fflush(stdout);

}

考虑用一个全局二维数组来保存这些中间值,避免一个状态重复计算多次,比如,用array[i][j]保LD(A,B,ij)的值,避免了以后每次计算LD(A,B,n,m)(其中n>i,m>j)时都要计算LD(A,B,ij)。
注意:其它的迭代也要考虑这个问题,尽量一种状态不要重复计算

最长上升子序列问题
给一个序列,求它的 一个递增子序列,使它的元素尽量多,如下图,最大上升子序列是1、2、5、7
技术分享
解法:
设d[i]为以i结尾的最长上升子序列的长度,那它的前一个元素是什么呢?设它为j,则 d[i]= max{d[j]} +1,且j<i,如:
d[7]=max{d[4],d[5],d[2],d[6],d[1]}+1
d[4]=max{d[2],d[1]}+1
d[5]=...
d[2]=...
d[6]=d[1]+1
d[1]=1
用递归求解,这里1在第一位,如果换其它的第一位,如:4、6、2、5、1、7,则
d[4]=1、d[2]=1、d[1]=1、d[6]=d[4]+1、d[5]=max{d[4]、d[2]}+1、...

01背包问题
01背包的状态转换方程:
f[i,j] = Max{ f[i-1,j-Wi]+Pi( j >= Wi ),  f[i-1,j] }
f[i,j]表示在前i件物品中选择若干件放在承重为 j 的背包中,可以取得的最大价值。
Pi表示第i件物品的价值。
决策:为了背包中物品总价值最大化,用上面的方程判断第i件物品是否应该放入背包中
题目描述:
有编号分别为a,b,c,d,e的五件物品,它们的重量分别是2,2,6,5,4,它们的价值分别是6,3,5,4,6,现在给你个承重为10的背包,如何让背包里装入的物品具有最大的价值总和?
name weight value 1 2 3 4 5 6 7 8 9 10
a 2 6 0 6 6 9 9 12 12 15 15 15
b 2 3 0 3 3 6 6 9 9 9 10 11
c 6 5 0 0 0 6 6 6 6 6 10 11
d 5 4 0 0 0 6 6 6 6 6 10 10
e 4 6 0 0 0 6 6 6 6 6 6 6

只要你能通过找规律手工填写出上面这张表就算理解了01背包的动态规划算法。

首先要明确这张表是至底向上,从左到右生成的。

为了叙述方便,用e2单元格表示e行2列的单元格,这个单元格的意义是用来表示只有物品e时,有个承重为2的背包,那么这个背包的最大价值是0,因为e物品的重量是4,背包装不了。

对于d2单元格,表示只有物品e,d时,承重为2的背包,所能装入的最大价值,仍然是0,因为物品e,d都不是这个背包能装的。

同理,c2=0,b2=3,a2=6。

对于承重为8的背包,a8=15,是怎么得出的呢?

根据01背包的状态转换方程,需要考察两个值,

一个是f[i-1,j],对于这个例子来说就是b8的值9,另一个是f[i-1,j-Wi]+Pi;

在这里,

 f[i-1,j]表示我有一个承重为8的背包,当只有物品b,c,d,e四件可选时,这个背包能装入的最大价值

f[i-1,j-Wi]表示我有一个承重为6的背包(等于当前背包承重减去物品a的重量),当只有物品b,c,d,e四件可选时,这个背包能装入的最大价值

f[i-1,j-Wi]就是指单元格b6,值为9,Pi指的是a物品的价值,即6

由于f[i-1,j-Wi]+Pi = 9 + 6 = 15 大于f[i-1,j] = 9,所以物品a应该放入承重为8的背包

java实现:

import java.util.ArrayList;

import java.util.List;

publicclass test1{

   private List<int[]> list = new ArrayList<int[]>();

   privateintweight = 0;

   public test1(List<int[]>list,int weight){

      this.list = list;

      this.weight = weight;

   }

   publicint getMaxValue(){

      return maxValue(list.size(), weight);

   }

   publicint maxValue(int num,int w){

      if(num==1){

         if(list.get(num-1)[0]<=w)

            returnlist.get(num-1)[1];

         else

            return 0;

      }

      if(list.get(num-1)[0]<=w){

         return ((maxValue(num-1,w-list.get(num-1)[0])+list.get(num-1)[1])>maxValue(num-1, w))?

               (maxValue(num-1,w-list.get(num-1)[0])+list.get(num-1)[1]):maxValue(num-1, w);

      }

      return maxValue(num-1, w);

   }

  

   publicstaticvoid main(String[]args){

      List<int[]>list = new ArrayList<int[]>();

      int weight = 10;

      list.add(newint[]{2,6});

      list.add(newint[]{2,3});

      list.add(newint[]{6,5});

      list.add(newint[]{5,4});

      list.add(newint[]{4,6});

      test1 test = new test1(list, weight);

      System.out.println(test.getMaxValue());

   }

}

说明:list中的数组包含两个元素,第一个是重量,第二个是价值

装配线调度
问题描述:
假如有两条生产线,每条生产线都有n个装配站,如A1j表示生产线1的第j个装配站,A2j表示生产线2的第j个装配站,这两个装配站(生产线1的第j个装配站与生产线2的第j个装配站)功能相同,但由于两条生产线采用的技术不同,所以这两个装配站的速度可能不同(也就是T1j与T2j可能不同)。产品在一条生产线从第j个装配站完成加工后可以送到本条生产线的第j+1个装配站,也可以送到另一条生产线的第j+1个装配站,如果送到同一条生产线的下一个装配站不需要切换时间(因为不需要卸装,时间很小,可以忽略),但如果送到另一条生产线就需要切换时间,设第1条生产线第j个装配站切换到另一条生产线的第j+1个装配站需要的时间为t1j。求一件产品被加工(从第1个装配站到到第n个装配站)所需的最小时间。
设MinT1j为经过第1个到第j个装配站所需的最小时间,且第j站在生产线1上,MinT2j为 经过第1个到第j个装配站所需的最小时间,且第j站在生产线2上 ,则有下面关系:
MinT1j = min{(MinT1(j-1)+T1j),(MinT2(j-1)+T1j+t2(j-1))}
MinT2j = min{(MinT2(j-1)+T2j),(MinT1(j-1)+T2j+t1(j-1))}
总最小时间为:
min{MinT1n,MinT2n}
递归可求

矩阵连乘
问题描述:
有n个矩阵:A1、A2....An,计算A1A2A3...An的乘积,矩阵相乘不满足交换律,但却满足结合律,如:A1(A2A3)A4A5(A6A7A8A9)....An与A1A2A3...An是相等的,不同的结合虽然结果相同,但所需的乘法运算次数却不相同,例如,A1是一个a*b矩阵,A2是一个b*c矩阵,A3是一个c*d矩阵,(A1A2)A3所需的乘法运算次数为a*b*c+a*c*d,A1(A2A3)所需的乘法运算次数为b*c*d+a*b*d。
求计算A1A2...An所需的最小乘法运算次数。
首先假如从位置k分开两部分:A1A2...Ak与A(k+1)A(k+2)...An, 左边部分结果是a*b矩阵,右边部分结果是b*c矩阵, 左边所需最小运算次数为min1k,右边所需最小运算次数为min(k+1)n,则总的运算次数为min1k+min(k+1)n+a*b*c。
所以,总的最小次数应该是上面表达式中k从1到n不断代入的所有结果中的最小值,
同理分别求左右的最小值,依次类推直到只有一个或两个矩阵相乘为止

整齐打印
问题描述:
有一段文章需要打印,纸上每行至多可以容纳M个字符,如果某一行包含从i到j的单词,i<j,单词之间有一个空格,则在行末多余的空格字符个数为:M-(j-i)-[Li+L(i+1)+.....Lj],Li表示单词i的长度,求所有行(最后一行除外)的行末多余字符个数的 立方的和最小。注意,单词顺序不能变,因为是一段文章,单词顺序变了就不是文章了。
如果不是求立方的和而是简单求和,那么每行尽量多放单词,能放就放便可使和最小,想想为什么。
这个问题解法与矩阵连乘相似,如单词序列为A1A2A3...An,那么也就相当于求它们如何组合使得要求的结果最小,比如一种结果为A1A2(A3A4A5)A6A7....(A(n-1)An)。

瓷砖覆盖问题
问题:
有一2*M的地板,用1*2的瓷砖覆盖该地板,请问有多少种覆盖方式。
解法一:
可以先判断M是否为奇数,如果是奇数,则竖着放的瓷砖肯定是奇数个,我们可以分别求竖着的瓷砖个数为1、3、5…的情况的覆盖方式种数,然后把它们相加,如求竖着的瓷砖个数为i情况,则横放的瓷砖个数为M-i,实际上是(M-i)/2个2和i个1求组合。同理如果M为偶,则分别求0、2、4…。
解法二:
设2*M的地板有f(M)种方式,如果第一块瓷砖竖着放,则f(M)=f(M-1),如果前两块瓷砖横着放,则f(M)=f(M-2),所以f(M)=f(M-1)+f(M-2),即满足斐波那契数列。

版权声明:本文为博主原创文章,未经博主允许不得转载。

动态规划

标签:

原文地址:http://blog.csdn.net/wudiyong22/article/details/47443885

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!