动态规划之背包和斐波那契

动态规划

  1. 动态规划问题的一般形式就是求最值问题。
  2. 求解动态规划的核心问题是穷举
  3. 动态规划的穷举有点特别,因为这类问题存在“重叠子问题”,所以可以减少穷举的过程,可以使用备忘录或者dpTable的方法。
  4. 通常解决动态规划问题要写出状态方程。
public class Fib {
    public static void main(String[] args) {
        System.out.println(fib(5));
        System.out.println(fibMemo(5));
        System.out.println(fibTable(4));
    }

    //暴力递归
    public static int fib(int n){
        if (n == 1 || n == 2){
            return n;
        }else{
            return fib(n-1) + fib(n-2);
        }
    }
    //带备忘录的递归
    private static int fibMemo(int n){
        int[] memo = new int[n+1];
        return helper(memo,n);
    }

    private static int helper(int[] memo, int n) {
        if (n == 1 || n == 2){
            return n;
        }
        if (memo[n]  != 0) return memo[n];
        memo[n] = helper(memo,n-1) + helper(memo,n-2);
        return memo[n];
    }
    //dp table
    private static int fibTable(int n){

        int[] dp = new int[n+1];
        dp[1] = 1;dp[2] = 1;
        for (int i = 3; i <= n; i++) {
            dp[i] = dp[i-1]+dp[i-2];
        }
        return dp[n];
    }
}

背包问题

背包容量5,各商品的重量分别为2,1,3,2,各商品价值12,10,20,15.
如何拿可以让背包装下价值最大。

思路

可以使用动态规划,暴力递归。可以写出状态方程

  1. 设F(i,j)是最优解的物品总价值,在不包括第i个物品的子集中,最优子集的价值为F(i-1,j);
  2. 在包括第i个物品的子集中,如果j-wi > 0 的话,即能够装得下此物品的话。最优子集是 要么放进去wi的价值大,要么不放wi的价值大,所以最优解为 max{ vi+F(i-1,j-wi) , F(i-1),j};
  3. 所以递推式为
    F(i,j) = { max(F(i-1,j),vi+F(i-1,j-wi)) ,j-wi>0 装的进去
    装不进去 { F(i-1,j) j-wi < 0;
public class DynamicPro {
    static int w = 5;//背包容量
    static int[] weight = {2,1,3,2};//各物品重量
    static int[] value = {12,10,20,15};//个物品的价值
    static int[][] dp = new int[weight.length+1][w+1];//记录之前的状态
    static List<Integer> result = new ArrayList<>();


    public static void main(String[] args) {
        traceback(4,5);
        System.out.println("最大价值为"+knapsack(4,5));
        System.out.println("选择的数组为"+result);

    }

    public static void traceback(int i,int w){
        if(i==0||w==0) return;
        if (w >= weight[i-1]){//如果装得下
            if (knapsack(i, w) == value[i - 1] + knapsack(i - 1, w - weight[i - 1])){
                result.add(i);
                traceback(i-1,w-weight[i-1]);
            }else{
                traceback(i-1,w);
            }
        }else{
            traceback(i-1,w);
        }
    }

    private static int knapsack(int n, int w) {
        for (int i = 1; i <= n ; i++) {
            for (int j = 1; j <= w; j++) {
                if (j < weight[i-1]){
                    dp[i][j] = dp[i-1][j];
                }else{
                    dp[i][j] = Math.max(dp[i-1][j],dp[i-1][j-weight[i-1]]+value[i-1]);
                }
            }
        }
        return dp[n][w];
    }
}

备忘录

即然耗时的原因是重复计算,那么我们可以造一个「备忘录」,每次算出某个子问题的答案后别急着返回,先记到「备忘录」里再返回;每次遇到一个子问题先去「备忘录」里查一查,如果发现之前已经解决过这个问题了,直接把答案拿出来用,不要再耗时去计算了。
代码

public class DynamicPro2 {
    static int w = 5;//背包容量
    static int[] weight = {2,1,3,2};//各物品重量
    static int[] value = {12,10,20,15};//个物品的价值
    static int[][] dp = new int[weight.length+1][w+1];//记录之前的状态
    static int[][] memo = new int[weight.length + 1][w + 1];
    static List<Integer> result = new ArrayList<>();


    public static void main(String[] args) {

        System.out.println("最大价值为"+knapsack(4,5));
        getArray();
        System.out.println("选择的数组为"+result);

    }



    private static int knapsack(int n, int j) {
        if (n == 0) return 0;
      if (memo[n][j]  == 0){
          if (j < weight[n-1]){
              memo[n][j] = knapsack(n-1,j);
          }else{
              memo[n][j] = Math.max(knapsack(n-1,j),value[n-1]+knapsack(n-1,j-weight[n-1]));
          }
      }
        return memo[n][j];
    }

    private static void getArray(){
        for (int i = weight.length; i > 0 ; i--) {
            if (memo[i][w] > memo[i-1][w]){
                result.add(i);
                w = w-weight[i-1];
            }
        }
    }
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值