算法分析与设计(四)动态规划(二)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/SakuraMashiro/article/details/78806442

动态规划的概念复习
每次决策依赖于当前状态,又随即引起状态的转移。一个决策序列就是在变化的状态中产生的,所以,这种多阶段最优化决策解决问题的过程就称为动态规划。

动态规划的思想和策略
将待求解的问题分解为若干个子问题,按顺序求解子阶段,前一子问题的解,为后一子问题的求解提供了有用的信息。
适合用动态规划求解的问题,经分解后得到的子问题往往不是互相独立的。(这一点与分治法不同)

能用动态规划求解的问题一般具有三个性质
1.最优化原理(最优子结构性质)
2.无后效性(当前状态一旦确定,就不受以后状态决策的影响)
3.有重叠子问题(子问题不相互独立,因而当前子问题的解可以为以后子问题的解提供参考)

动态规划求解的基本步骤
1.划分阶段
2.确定状态和状态变量
3.确定决策并写出状态转移方程
4.寻找边界条件

例题深入

1.字符串解码
问题描述:一个包含字母的消息被加密后变成了只包含数字的字符串,我们现在知道加密的规则:A–>1 ; B–>2 …… Z–>26 ;
现在给定一个已经被加密的只包含数字的字符串,求出该字符串有多少种被解密的方法。例如 “12” -> AB 或者 “12”->L 。

分析:假设定义一个数组,dp[i]为到第i个数字所能够组成的所有解码方式的个数,那么对于dp[i+1]来说,如果第i个数字和第i+1个数字不能构成一个字符的编码,那么第i+1个数字单独解码,解码方式的个数和有i个数字是相同的,即 dp[i+1] = dp[i] ;反之,如果第i个数字和第i+1个数字能构成一个字符的编码,那么解码方式的个数就等于前i-1个数字的解码方式个数加上前i个数字的解码方式的个数,即dp[i+1] = dp[i] + dp[i-1],因为此时你可以选择第i+1个数字单独解码,那么方法数等于dp[i],或者第i个和第i+1个一起编码,方法数等于dp[i-1]。

代码实现

#include<iostream>
#include<string>
#include<vector>
using namespace std;

/**
*  求解字符串的解码方法总数
*  @param str 需要解码的字符串
*  @return int 解码方法的总数
*/
int Decod_num(string& str){

    //定义一个数组记录解码方式的个数
    vector<int> vec( str.size() , 1 );
    //只有一个数字,解码方式就一种
    if( str.size() < 2 ){
        return 1 ;
    }
    //26以内的数字,解码方式两种
    if( str[0] == '1' || (str[0] == '2' && str[1] <= '6')){
        vec[1] = 2 ;
    }

    int i ;
    int tmp ;
    //动态规划求解过程
    for( i = 2 ; i < str.size() ; i ++ ){
        //判断是合法的字符
        if( str[i] >= '0' && str[i] <= '9'){
            //状态转移1,i个数字的解码方法数至少是前i-1个数字的解码方法数
            vec[i] = vec[i-1];
        }else{
            return 0 ;
        }

        tmp = str[i-1] - '0';
        tmp = tmp*10 + str[i]-'0';
        //判断最后两个数字是否能构成一个字符的编码
        if( str[i-1] != '0' && tmp <= 26){
            //状态转移2, i个数字的解码方法数等于前i-1个数字的解码方法数加上前i-2个数字的解码方法数
            vec[i] += vec[i-2];
        }
    }
    //数组的最后一位即当前字符串的解码方法总数
    return vec[str.size()-1];
}

2.矩阵最小路径和
问题:给定一个二维矩阵,矩阵的每个元素指定了走到该出所需要的代价,要你从矩阵左上角到右下角,寻找代价最小的一条路径。

分析:到达矩阵的一个点,有两种走法,一是从上面一个格子走过来,一是从左边的格子走过来(边界点除外)。那么,到达一点的最短路径,要么就是到达该点左边一个点的最小代价加上该点的代价,要么就是到达该点上面一个点的最小代价加上该点的代价,两者中的最小值。
即状态转移方程
dp[i][j] = min( dp[i-1][j] + vec[i][j] , dp[i][j-1] + vec[i][j] )

代码实现

/**
*  求解矩阵从左上角到右下角的最小路径代价
*  @param vec 矩阵的二维数组
*  @return int 最小的路径代价
*/
int MinPathSum( vector<vector<int>> & vec ){
    vector<vector<int>> dp( vec.size() );
    int i,j ;
    //初始化动态规划需要的数组
    for( i = 0 ; i < vec.size() ; i ++ ){
         dp[i].assign(vec[i].size(),numeric_limits<int>::max());

    }
    dp[0][0] = vec[0][0];

    //初始化边界值
    for( i = 1 ; i < vec.size() ; i++ ){
         dp[i][0] = vec[i][0]+dp[i-1][0];
    }
    for( j = 1 ; j < vec[0].size() ; j++ ){
         dp[0][j] = vec[0][j]+dp[0][j-1];
    }

    //求解过程
    int temp ;
    for( i = 1 ; i < vec.size() ; i ++ ){
         for( j = 1 ; j < vec[0].size() ; j ++  ){
              tmp = min(vec[i][j] + dp[i][j-1] , vec[i][j] + dp[i-1][j]);
              if( tmp < dp[i][j] ){
                  dp[i][j] = temp ;
              }
         }
    }
    return dp[vec.size()-1][vec[0].size()-1];
}

3.最大子数组乘积
问题:给定一个整数数组,求解乘积最大的子数组的值

分析:由于数组中可能出现负数,所以当前最大值,可能是之前最大乘以当前值(如果之前最大乘积为正数,且当前数也为正数),也可能是之前最小乘以当前值(如果之前最小乘积为负数,且当前值也为负数,负负得正),也可能是当前数。
所以为了得到全局最优,我们需要两个数组来存储局部最优值,一个保存局部最大值(正数),一个保存局部最小值(负数),并不断更新两个局部最优。

代码实现

/**
*  求解最大子数组乘积
*  @param vec 一维数组
*  @return int 最大乘积
*/
int maxProduct( vector<int>& vec){
    if( vec.size() == 0 ){
        return 0 ;
    }
    //一维规划,但是需要两个数组来保存两个局部最优值,以得到全局最大
    vector<int> maxcur(vec.size(),0);
    vector<int> mincur(vec.size(),0);
    maxcur[0] = vec[0];
    mincur[0] = vec[0];
    int maxproduct = vec[0];
    int i , temp ;

    for( i = 1 ; i < vec.size() ; i ++ ){
        //更新局部最大值
        maxcur[i] = max( vec[i] , max(maxcur[i-1]*vec[i],mincur[i-1]*vec[i])); 
        //更新局部最小值
        mincur[i] = min( vec[i] , min(mincur[i-1]*vec[i],maxcur[i-1]*vec[i]));

        //更新全局最大值
        maxproduct = max( maxcur[i] , maxproduct );
    }
    return maxproduct ;
}

猜你喜欢

转载自blog.csdn.net/SakuraMashiro/article/details/78806442
今日推荐