算法之动态规划

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

动态规划的思想与策略:
将待求解的问题分解为若干个子问题(阶段),按顺序求解子阶段,前一子问题的解,为后一子问题的求解提供了有用的信息。在求解任一子问题时,列出各种可能的局部解,通过决策保留那些有可能达到最优的局部解,丢弃其他局部解。依次解决各子问题,最后一个子问题就是初始问题的解。

由于动态规划解决的问题多数有重叠子问题这个特点,为减少重复计算,对每一个子问题只解一次,将其不同阶段的不同状态保存在一个二维数组中。

在上代码之前说个例子:
假设你有1,2,3(元)三种种类的零钱,数量不限,你需要找零6元,请问有多少种方法?

很显然,是7种。我们就来说一说这个思想。

第一类:只用1元或2元或3元的零钱去找零,均为1种,共3种
第二类:只用1元,2元二种找零,会出现2种,
第三类:只用1元,3元二种找零,会出现1种,
第四类:用1元,2元,3元三种找零,会出现1种,

所以答案是7种办法。我们换一个角度进

引用块内容

行思考,将其分为3类:

第一类:只能用1元去找零,共1种
第二类:可以用1元,2元找零,会出现4种,
第三类:可以用1元,2元,3元找零,会出现7种,

在这里就会有疑问,第三类不是直接和问题一样了吗?但是不难发现的是:在第二类的结果中包含了第一类的结果,第三类的结果中也包含了第二类的结果。
第一类与第二类,第二类与第三类存在的差异就是所用的零钱种类数。
换一个角度看,可以将第三类分为2种情况:
(1)使用3元零钱:3,1,1,1—–3,2,1—–3,3(三种方法)
(2)不使用3元零钱:与第二类相同
同理将第二类分为同样的2种情况:
(1)使用2元零钱:2,2,2—–2,2,1,1—–2,1,1,1,1(三种方法)
(2)不使用2元零钱:与第一类相同
第一类其实同样可以分为2种情况:
(1)使用1元零钱:1,1,1,1,1,1(1种情况)
(2)不使用1元零钱:0种

如果将问题依次解决,那么最后一个问题的解就是问题初始的解,这也正是动态规划的魅力所在。

上面这一段文字或许你没读的太懂,所以通过代码示例进行进一步的解读。

public class 动态规划_找零问题 {

    public static void main(String[] args) {

        Exchange exchange = new Exchange();
        int a[] = {1,2,3};//零钱面值
        int count = exchange.countWays(a, 3, 6);//使用前3种零钱找零6元
        System.out.println(count);
    }

}

//penny存放零钱的数组  n所用数组零钱种类的数量  aim所需找钱数
class Exchange {  
    public int countWays(int[] penny, int n, int aim) { 
        //数据出错 
        if(n == 0 || penny == null || aim < 0){  
            return 0;     
        }  
        /**
        *二维数组的大小设定为零钱的种类数*(找零数+1)
        所加的1是找零0元
        */
        int[][] pd = new int[n][aim+1]; 
        //将找零0元的列初始化为1(无论是多少零钱去找零0元,都不存在,所以都是一种情况)
        for(int i=0;i<n;i++){  
            pd[i][0] = 1;     
        }  
        //由于最小找零是1,所以第0行的每一列都初始化为1
         /**就像这样
         *        0     1     2     3     4     5    6
         *(1元)【1】 【1】 【1】 【1】 【1】 【1】 【1】
         */
        for(int i = 1; penny[0] * i <= aim; i++){  
            pd[0][penny[0] * i] = 1;     
        }
       /**
         *现在假设dp[n][m]为使用前n种货币凑成的m的种数,那么就会有两种情况:
         *使用第n种货币:dp[n-1][m]+dp[n-1][m-peney[n]]
         *不用第n种货币:dp[n-1][m],为什么不使用第n种货币呢,因为penney[n] > m。(就好比你需要找零2元,假设你有一种3元的零钱,你就使用不到)
         *这样就可以求出当m>=penney[n]时 dp[n][m] = dp[n-1][m]+dp[n][m-peney[n]]
         *否则,dp[n][m] = dp[n-1][m]
         */
        for(int i = 1; i < n; i++){  
            for(int j = 0; j <= aim; j++){  
                if(j >= penny[i]){  
                    pd[i][j] = pd[i-1][j] + pd[i][j-penny[i]];   
                }else{  
                    pd[i][j] = pd[i-1][j];  
                }  
            }  
        }  
        return pd[n-1][aim];  
    }

或许还是会有一丁点的疑惑(那我就再仔仔细细的分析一波):
第二类(只用1元,2元):

  • 如果需要找一元钱,则不需要用到2元钱。那么和只用一元钱是一样的,即dp[n][m] = dp[n-1][m]。是1种。
  • 如果需要找两元钱,不用到2块钱,即一种。
    用到2元钱。然后再用(1元,2元)去找0元钱。1种。
    相加共2种。
  • 如果需要找三元钱,不用到2块钱,即一种。
    用到2元钱。然后再用(1元,2元)去找1元钱。1种。
    相加共2种。
  • 如果需要找四元钱,不用到2块钱,即一种。
    用到2元钱。然后再用(1元,2元)去找2元钱(见上)。2种。
    相加共3种。
  • 如果需要找五元钱,不用到2块钱,即一种。
    用到2元钱。然后再用(1元,2元)去找3元钱(见上)。2种。
    相加共3种。
  • 如果需要找六元钱,不用到2块钱,即一种。
    用到2元钱。然后再用(1元,2元)去找4元钱(见上)。即dp[n][m-peney[n]],3种。
    相加共4种。

第三类(用1,2,3):

  • 如果需要找一元钱,则不需要用到3元钱。即dp[n][m] = dp[n-1][m]。是1种。
  • 如果需要找两元钱,不用到3块钱,即2种。
  • 如果需要找三元钱,不用到3块钱,即2种。
    用到3元钱。然后再用(前三种)去找0元钱。1种。
    相加共3种。
  • 如果需要找四元钱,不用到3块钱,即3种。
    用到3元钱。然后再用(前三种)去找1元钱。1种。
    相加共4种。
  • 如果需要找五元钱,不用到3块钱,即3种。
    用到3元钱。然后再用(1元,2元)去找2元钱(见上)。2种。
    相加共5种。
  • 如果需要找六元钱,不用到3块钱,即4种。
    用到3元钱。然后再用(前三种)去找3元钱(见上)。3种。
    相加共7种。

那么算到最后呢,数组是这样子的:
0 1 2 3 4 5 6
(1元) a0 【1】 【1】 【1】 【1】 【1】 【1】【1】
(2元) a1 【1】 【1】 【2】 【2】 【3】 【3】【4】
(3元) a2 【1】 【1】 【2】 【3】 【4】 【5】【7】

如果还有疑问,对不起,我已经把能讲的讲完了。。。。。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值