动态规划算法及其基本实现

动态规划(Dynamic Programming)算法的核心思想是:将大问题划分为小问题进行解决,从而一步步获取最优解的处理算法。

动态规划算法与分治算法类似。其基本思想也是将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。

与分治法不同的是,适合于用动态规划求解的问题,经分解得到子问题往往不是互相独立的。即下一个子阶段的求解是建立在上一个子阶段的解的基础上,进行进一步的求解。

动态规划可以通过填表的方式来逐步推进,得到最优解。

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

动态规划最经典的就是背包问题:

背包问题可以通过动态规划算法来实现。

背包问题:

背包问题主要是指一个给定容量的背包、若干具有一定价值和重量的物品,如何选择物品放入背包使物品的价值最大。

其中又分01背包和完全背包。

  • 完全背包:每种物品可以放多次。
  • 01背包:每个物品最多放一个。

无限背包可以转化为01背包。

i:行坐标,代表物品。

j:列坐标,代表物品的容量。

v[i][j]:前 i 个物品中能够装入容量为j的背包中的最大价值。

装入顺序是从上到下,从左到右。

利用动态规划来解决。每次遍历到的第i个物品,根据w[i]和v[i]来确定是否需要将该物品放入背包中。即对于给定的n个物品,设v[i]、w[i]分别为第i个物品的价值和重量,C为背包的容量。

令v[i][j]表示在前 i 个物品中能够装入容量为j的背包中的最大价值

可以推出下面的结论:

1 v[i][0]=v[0][j]=0;
表示填入表第一行和第一列是0。

2 当w[i] > j 时:v[i][j]=v[i-1][j]   
该公式描述的是:当准备加入商品的容量大于当前背包的容量时,就直接使用上一个单元格的装入策略。

3 当j >=w[i]时:v[i][j]=max{v[i-1][j], v[i]+v[i-1][j-w[i]]}  
该公式描述的是:当准备加入的商品的容量小于等于当前背包的容量的装入的策略。

v[i-1][j]:就是上一个单元格的装入的最大值。

v[i]:表示当前要装入商品的价值。

v[i-1][j-w[i]] :表示在前 i-1 个物品中能够装入容量为 j-w[i] 的背包中的最大价值。

代码实现如下:


 

//01背包问题 
public static void beibao01(){
        int[] w = {1, 4, 3};//物品的重量
        int[] val = {1500, 3000, 2000};//物品的价值
        int m = 4;//背包的容量,这里指的是能放多重的东西
        int n = val.length;//物品的个数
        //v[i][j]表示在前i个物品中能够装入容量为j的背包中的最大价值
        int[][] v = new int[n + 1][m + 1];// 4 5
        //用于存放符合条件的路径
        int [][] path=new int [n+1][m+1];


        //由之前的公式来动态规划处理
        for (int i=1;i<v.length;i++){//i=2 不处理第一行i从第一行开始
            for (int j=1;j<v[0].length;j++){
                if(w[i-1]>j){//因为我们程序i 是从1开始的,w[]数组的下标识从0开始的,所以是w[i-1]
                    v[i][j]=v[i-1][j];
                }else {
                    if(v[i-1][j]<val[i-1]+v[i-1][j-w[i-1]]){//符合情况,val[i-1]+v[i-1][j-w[i-1]]
                        v[i][j]=val[i-1]+v[i-1][j-w[i-1]];
                        path[i][j]=1;
                    }else {
                        v[i][j]=v[i-1][j];
                    }
                }
            }
        }
        for (int i=0;i<v.length;i++){
            for (int j=0;j<v[i].length;j++){
                System.out.print(v[i][j]+" ");
            }
            System.out.println();
        }
        int i=path.length-1;//行的最大下标
        int j=path[0].length-1;//列的最大下标
        while (i>0&&j>0){//从path的最后开始找
            if(path[i][j]==1){
                System.out.println("把第"+i+"个物品放入了背包");
                j=j-w[i-1];
            }
            i--;
        }
    }

运行结果:

 

如果是完全背包问题可以由01背包问题改进得到:

把当j >=w[i]时:v[i][j]=max{v[i-1][j], val[i]+v[i-1][j-w[i]]}  

改为:当j >=w[i]时:v[i][j]=max{v[i-1][j], val[i]+v[i][j-w[i]]}  

代码如下:

//完全背包问题
    public static void beibaowanquan(){
        int[] w = {1, 4, 3};//物品的重量
        int[] val = {1500, 3000, 2000};//物品的价值
        int m = 4;//背包的容量,这里指的是能放多重的东西
        int n = val.length;//物品的个数
        //v[i][j]表示在前i个物品中能够装入容量为j的背包中的最大价值
        int[][] v = new int[n + 1][m + 1];// 4 5
        //用于存放符合条件的路径
        int [][] path=new int [n+1][m+1];


        //由之前的公式来动态规划处理
        for (int i=1;i<v.length;i++){//i=2 不处理第一行i从第一行开始
            for (int j=1;j<v[0].length;j++){
                if(w[i-1]>j){//因为我们程序i 是从1开始的,w[]数组的下标识从0开始的,所以是w[i-1]
                    v[i][j]=v[i-1][j];
                }else {
                    //需要修改的地方在这
                    if(v[i-1][j]<val[i-1]+v[i][j-w[i-1]]){//由基本的01背包问题转化而来,可以多次重复取无限次,从当前行就能开始取,不必从前面去取val[i-1]+v[i][j-w[i-1]],就能多次取重复值
                        v[i][j]=val[i-1]+v[i][j-w[i-1]];
                        path[i][j]=1;
                    }else {
                        v[i][j]=v[i-1][j];
                    }
                }
            }
        }
        for (int i=0;i<v.length;i++){
            for (int j=0;j<v[i].length;j++){
                System.out.print(v[i][j]+" ");
            }
            System.out.println();
        }
        int i=path.length-1;//行的最大下标
        int j=path[0].length-1;//列的最大下标
        while (i>0&&j>0){//从path的最后开始找
            if(path[i][j]==1){
                System.out.println("把第"+i+"个物品放入了背包");
                j=j-w[i-1];
            }
            i--;
        }
    }

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值