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

如何运用动态规划解题

时间:2017-08-15 10:20:39      阅读:151      评论:0      收藏:0      [点我收藏+]

标签:设计   class   mil   names   --   输入   div   ret   cin   

本文内容整理自中国大学MOOC郭炜老师的程序设计与算法(二)


 

首先由数字三角形问题出发( ̄︶ ̄)↗ ,题目描述如下

         7 
           3   8 
         8   1   0 
      2   7   4   4 
   4   5   2   6   5

在上面的数字三角形中寻找一条从顶部到底部的路径,使得路径上所经过的数字之和最大。路径上的每一步都只能往左下或右下走。只需要求出这个最大和即可,不必给出具体路径。(三角形的行数大于1小于100,数字为0~99)

输入:

第一行是数字三角形的行数,接下来 n 行是数字三角形中的数字。  

  比如:

  5

  7

  3  8

  8 1 0

  2 7 4 4

  4 5 2 6 5  

输出:

  最大和

分析:

1)首先最容易想到的是递归算法,第n行第n列到底部的路径最大和为:自身+第n+1行最大的;临界条件:最后一行的路径最大和即为自身。

递归参考代码

///递归
#include <iostream>

using namespace std;
int n;
int MaxSum(int i,int j,int a[][101]);
int main()
{
    int a[101][101];

    cin>>n;
    for(int i=0;i<n;i++){
        for(int j=0;j<i+1;j++){
            cin>>a[i][j];
        }
    }
    cout<<MaxSum(0,0,a);
    return 0;
}
int MaxSum(int i,int j,int a[][101]){
if(i==n-1){
    return a[i][j];
}else if(i<n-1){
    int x=MaxSum(i+1,j,a);
    int y=MaxSum(i+1,j+1,a);
    return max(x,y)+a[i][j];
}

}

2)可是该递归方法有很多很多重复的计算,所以我们想到可以记下已经算过的,当用到时直接拿过来用,这样就避免了大量的重复运算。

 

///记忆递归型动规
#include <iostream>

using namespace std;
int n;
int b[101][101];//记忆数组用来存已经计算出来的结果
int MaxSum(int i,int j,int a[][101]);
int main()
{
    int a[101][101];

    cin>>n;
    for(int i=0;i<n;i++){
        for(int j=0;j<i+1;j++){
            cin>>a[i][j];
            b[i][j]=-1;//初始化为-1
        }
    }
    cout<<MaxSum(0,0,a);
    return 0;
}
int MaxSum(int i,int j,int a[][101]){
if(b[i][j]!=-1){//如果不等于-1说明已经计算过,所以直接返回
    return b[i][j];
}
else
{
    if(i==n-1){
    return b[i][j]=a[i][j];
    }
    else if(i<n-1)
    {
    int x=MaxSum(i+1,j,a);
    int y=MaxSum(i+1,j+1,a);
    return b[i][j]=max(x,y)+a[i][j];
    }
}
}

 

3)其实呢,一般都是用递推的方式来推出动态规划算法的,用循环递推不但比递归快,而且还能节省空间,写起来也比较方便,但是还是基于递归的思想才能递推出递推式。

第一步:在草稿纸或者脑子里想出解决该题的递归函数。

第二步:递归函数有n个参数,就定义一个n维的数组,数组的下标是,递归函数参数的取值范围,数组元素的值是递归函数的返回值。

第三步:根据边界条件,初始化n维数组。

第四步:得到递推式,根据递推式逐步填充数组,相当于递归的逆过程。

如本题:

///第一步写递归函数已经在上面写完了
#include <iostream>
#include <algorithm>
using namespace std;

int main()
{
    int i,j;
    int n;
    int D[101][101];
    ///第二步,因为递归函数有行i和列j两个参数所以定义一个二维数组
    int maxSum[101][101];
    cin>>n;
    for(i=1;i<=n;i++){
        for(j=1;j<=i;j++){
            cin>>D[i][j];
        }
    }

    ///第三步根据边界条件初始化二维数组
    for(int i=1;i<=n;i++){
        maxSum[n][i]=D[n][i];
    }

    /**第四步,根据递归函数推出递推式
                a[i][j],i==n-1;
    dp[i][j]=
                max(dp[i+1][j],dp[i+1][j+1])+a[i][j];
    */
    ///得到递推式,把递推式写出来
    for(int i=n-1;i>=1;i--){
        for(int j=1;j<=i;j++){
                maxSum[i][j]=max(maxSum[i+1][j],maxSum[i+1][j+1])+D[i][j];
        }
    }
    cout<<maxSum[1][1]<<endl;
    return 0;
}

4)往往由递推式写出的程序可以进行空间优化,如样例输入

①用一维记忆数组代替多维记忆数组

其一维记忆数组为maxSum={4,5,2,6,5}

那么算n-1行经过2的路径的最大和,在4和5中选出最大数,将结果存在4的位置,即maxSum={7,5,2,6,5}。这样并不会影响其他的路径和,比如第n-1行经过7的路径的最大和,是在5和2中选出最大数。

#include <iostream>
#include <algorithm>
using namespace std;

int main()
{
    int i,j;
    int n;
    int D[101][101];
    int maxSum[101];///此处由二维数组变成了一维数组
    cin>>n;
    for(i=1;i<=n;i++){
        for(j=1;j<=i;j++){
            cin>>D[i][j];
        }
    }

    for(int i=1;i<=n;i++){
        maxSum[i]=D[n][i];
    }

    for(int i=n-1;i>=1;i--){
        for(int j=1;j<=i;j++){
                maxSum[j]=max(maxSum[j],maxSum[j+1])+D[i][j];///取i下面和右面的最大值+本身,存在i下面数的位置
        }
    }
    cout<<maxSum[1]<<endl;
    return 0;
}

②删去一维记忆数组,用保存输入数据的数组的最后一行当作记忆数组保存计算结果

#include <iostream>
#include <algorithm>
using namespace std;

int main()
{
    int i,j;
    int n;
    int D[101][101];
    cin>>n;
    for(i=1;i<=n;i++){
        for(j=1;j<=i;j++){
            cin>>D[i][j];
        }
    }
    //因为一维记忆数组初始化是和D数组第n行一样,而且D数组第n行只在n-1才会用到
    for(int i=n-1;i>=1;i--){
        for(int j=1;j<=i;j++){
                D[n][j]=max(D[n][j],D[n][j+1])+D[i][j];///取i下面和右面的最大值+本身,存在第n行
        }
    }
    cout<<D[n][1]<<endl;
    return 0;
}

4)那么什么样的问题适合用动态规划呢?

①问题具有最优子结构性质。

如果问题的最优解所包含的子问题的解也是最优的,我们就称该问题具有最优子结构性质。  

②无后效性。

当前的若干状态的值一旦确定,则此后过程的演变就只和这若干个状态的值有关,和之前是采取哪种手段或经过哪条路径演变到当前的这若干个状态,没有关系。

如何运用动态规划解题

标签:设计   class   mil   names   --   输入   div   ret   cin   

原文地址:http://www.cnblogs.com/LuRenJiang/p/7360498.html

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