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

来源:互联网 发布:站桩 知乎 编辑:程序博客网 时间:2024/05/23 05:08

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

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

能用动态规划求解的问题一般具有三个性质
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 ;}
阅读全文
0 0
原创粉丝点击