01背包问题

小偷有一个容量为W的背包,有n件物品,第i个物品价值vi,且重wi
目标: 找到xi使得对于所有的xi = {0, 1}
sum(wi*xi) <= W, 并且 sum(xi*vi)最大

递归代码

package org.fan.learn.dp;

/**
 * Created by fan on 2016/9/13.
 */
public class Bag {


    public static int[][] result;

    public static final int MAX_INT = 1000000;

    public static final int BAG_MAX_WEIGHT = 12;
    public static final int PRODUCT_COUNTS = 5;
//    public static final int[] WEIGHTS = {1,9,3};
//    public static final int[] VALUES = {6,2,3};
//    public static final int[] WEIGHTS = {3,4,5};
//    public static final int[] VALUES = {3,4,6};
    public static final int[] WEIGHTS = {2, 2, 6, 5, 4};
    public static final int[] VALUES = {6, 3, 5, 4, 6};
    //这个返回的是有idx个物品时的最大价值,w表示这时书包还可以容纳的物品的重量
    public static int search(int idx, int w) {
        if (idx < 0) {
            return 0;
        }
        if (WEIGHTS[idx] > w) {
            return search(idx - 1, w);
        }
        //search(idx-1, w)表示没有选idx
        //search(idx-1,w-WEIGHTS[idx])表示选了idx
        int a = search(idx - 1, w);
        int b = search(idx - 1, w - WEIGHTS[idx]) + VALUES[idx];
        return Math.max(a, b);
    }

    //这个返回的是有idx物品时的最大价值,w表示这时书包中的重量
    public static int search2(int idx, int w) {
        if (idx >= PRODUCT_COUNTS) {
            return 0;
        }
        int a = 0, b = 0;
        //search(idx+1, w)表示没有选idx
        //search(idx+1,w+WEIGHTS[idx])表示选了idx
        if (w + WEIGHTS[idx] <= BAG_MAX_WEIGHT) {
            a = search2(idx + 1, w + WEIGHTS[idx]) + VALUES[idx];
        }
        b = search2(idx + 1, w);
        return Math.max(a, b);
    }

    //推荐这个写法
    //这个返回的是有idx个物品时的最大价值,w表示这时书包还可以容纳的物品的重量
    public static int search3(int idx, int w) {
        if (idx < 0) {
            return 0;
        }
        //result[idx][w]表示从前idx(包含idx)个东西,达到重量上限w,可以获得的最大价值
        if (result[idx][w] >= 0) {
            return result[idx][w];
        }

        //冗余的地方,主要是因为有两个search。而n!中是没有冗余的
        //(idx-1, w-W[idx])
        //(idx-1, w-W[idx])

        int a = 0, b = 0;
        //search(idx-1, w)表示没有选idx
        //search(idx-1,w-WEIGHTS[idx])表示选了idx
        if (WEIGHTS[idx] <= w) {
            a = search3(idx - 1, w - WEIGHTS[idx]) + VALUES[idx];;
        }
        b = search3(idx - 1, w);
        /*
        上面的代码不能这么写:
        if (WEIGHTS[idx] > w) {
            a = search3(idx - 1, w);
        }
        b = search3(idx - 1, w - WEIGHTS[idx]) + VALUES[idx];
        这么写只有WEIGHTS[idx] > w时才会调用search3(idx - 1, w),
        其实当WEIGHTS[idx] < w时,也可以调用search3(idx - 1, w)
         */
        result[idx][w] = Math.max(a, b);
        return result[idx][w];
    }

    public static void main(String[] args) {
        result = new int[PRODUCT_COUNTS][BAG_MAX_WEIGHT+1];
        for (int i = 0; i < PRODUCT_COUNTS; i++) {
            for (int j = 0; j < BAG_MAX_WEIGHT+1; j++) {
                result[i][j] = -1;
            }
        }
        System.out.println(search(PRODUCT_COUNTS-1, BAG_MAX_WEIGHT));
        System.out.println(search2(0, 0));
        System.out.println(search3(PRODUCT_COUNTS - 1, BAG_MAX_WEIGHT));
    }
}

执行结果:
17
17
17

递推代码

    //这是result[i][j]的含义:前i个东西,获得w重量时的最大价值
    public static int searchDitui(int idx, int w) {
        result[0][0] = 0;
        for (int i = 1; i <= BAG_MAX_WEIGHT; i++) {
            if (WEIGHTS[0] > i) {
                //当前0号东西的重量已经超过目标重量j,因此不能取0号东西,获得的价值为0
                result[0][i] = 0;
            } else {
                //WEIGHTS[0] <= i此时可以拿0号东西,获得0号东西的价值
                result[0][i] = VALUES[0];
            }
        }

        for (int i = 1; i < PRODUCT_COUNTS; i++) {
            result[i][0] = 0;
            for (int j = 1; j <= BAG_MAX_WEIGHT; j++) {
                //不取i号东西,因此跟前一个东西的价值相同
                result[i][j] = result[i-1][j];
                //只有当i号东西的重量小于等于目标重量j时才取i号东西
                if (WEIGHTS[i] <= j) {
                    result[i][j] = Math.max(result[i-1][j-WEIGHTS[i]]+VALUES[i], result[i][j]);
                }
            }
        }
        return result[idx][w];
    }

递推优化

使用滚动数组

//这是result[i][j]的含义:前i个东西,获得w重量时的最大价值
    public static int searchDituiYouhua(int idx, int w) {
        result[0][0] = 0;
        for (int i = 1; i <= BAG_MAX_WEIGHT; i++) {
            if (WEIGHTS[0] > i) {
                //当前0号东西的重量已经超过目标重量j,因此不能取0号东西,获得的价值为0
                result[0][i] = 0;
            } else {
                //WEIGHTS[0] <= i此时可以拿0号东西,获得0号东西的价值
                result[0][i] = VALUES[0];
            }
        }

        for (int i = 1; i < PRODUCT_COUNTS; i++) {
            result[i%2][0] = 0;
            for (int j = 1; j <= BAG_MAX_WEIGHT; j++) {
                //不取i号东西,因此跟前一个东西的价值相同
                result[i%2][j] = result[(i-1)%2][j];
                //只有当i号东西的重量小于等于目标重量j时才取i号东西
                if (WEIGHTS[i] <= j) {
                    result[i%2][j] = Math.max(result[(i-1)%2][j-WEIGHTS[i]]+VALUES[i], result[i%2][j]);
                }
            }
        }
        return result[idx%2][w];
    }

完整代码

package org.fan.learn.dp;

/**
 * Created by fan on 2016/9/13.
 */
public class Bag {


    public static int[][] result;

    public static final int MAX_INT = 1000000;

    public static final int BAG_MAX_WEIGHT = 10;
    public static final int PRODUCT_COUNTS = 3;
//    public static final int[] WEIGHTS = {1,9,3};
//    public static final int[] VALUES = {6,2,3};
    public static final int[] WEIGHTS = {3,4,5};
    public static final int[] VALUES = {3,4,6};
//    public static final int[] WEIGHTS = {2, 2, 6, 5, 4};
//    public static final int[] VALUES = {6, 3, 5, 4, 6};
    //这个返回的是有idx个物品时的最大价值,w表示这时书包还可以容纳的物品的重量
    public static int search(int idx, int w) {
        if (idx < 0) {
            return 0;
        }
        if (WEIGHTS[idx] > w) {
            return search(idx - 1, w);
        }
        //search(idx-1, w)表示没有选idx
        //search(idx-1,w-WEIGHTS[idx])表示选了idx
        int a = search(idx - 1, w);
        int b = search(idx - 1, w - WEIGHTS[idx]) + VALUES[idx];
        return Math.max(a, b);
    }

    //这个返回的是有idx物品时的最大价值,w表示这时书包中的重量
    public static int search2(int idx, int w) {
        if (idx >= PRODUCT_COUNTS) {
            return 0;
        }
        int a = 0, b = 0;
        //search(idx+1, w)表示没有选idx
        //search(idx+1,w+WEIGHTS[idx])表示选了idx
        if (w + WEIGHTS[idx] <= BAG_MAX_WEIGHT) {
            a = search2(idx + 1, w + WEIGHTS[idx]) + VALUES[idx];
        }
        b = search2(idx + 1, w);
        return Math.max(a, b);
    }

    //推荐这个写法
    //这个返回的是有idx个物品时的最大价值,w表示这时书包还可以容纳的物品的重量
    public static int search3(int idx, int w) {
        if (idx < 0) {
            return 0;
        }
        //result[idx][w]表示从前idx(包含idx)个东西,达到重量上限w,可以获得的最大价值
        if (result[idx][w] >= 0) {
            return result[idx][w];
        }

        //冗余的地方,主要是因为有两个search。而n!中是没有冗余的
        //(idx-1, w-W[idx])
        //(idx-1, w-W[idx])

        int a = 0, b = 0;
        //search(idx-1, w)表示没有选idx
        //search(idx-1,w-WEIGHTS[idx])表示选了idx
        if (WEIGHTS[idx] <= w) {
            a = search3(idx - 1, w - WEIGHTS[idx]) + VALUES[idx];;
        }
        b = search3(idx - 1, w);
        /*
        上面的代码不能这么写:
        if (WEIGHTS[idx] > w) {
            a = search3(idx - 1, w);
        }
        b = search3(idx - 1, w - WEIGHTS[idx]) + VALUES[idx];
        这么写只有WEIGHTS[idx] > w时才会调用search3(idx - 1, w),
        其实当WEIGHTS[idx] < w时,也可以调用search3(idx - 1, w)
         */
        result[idx][w] = Math.max(a, b);
        return result[idx][w];
    }

    //这是result[i][j]的含义:前i个东西,获得w重量时的最大价值
    public static int searchDitui(int idx, int w) {
        result[0][0] = 0;
        for (int i = 1; i <= BAG_MAX_WEIGHT; i++) {
            if (WEIGHTS[0] > i) {
                //当前0号东西的重量已经超过目标重量j,因此不能取0号东西,获得的价值为0
                result[0][i] = 0;
            } else {
                //WEIGHTS[0] <= i此时可以拿0号东西,获得0号东西的价值
                result[0][i] = VALUES[0];
            }
        }

        for (int i = 1; i < PRODUCT_COUNTS; i++) {
            result[i][0] = 0;
            for (int j = 1; j <= BAG_MAX_WEIGHT; j++) {
                //不取i号东西,因此跟前一个东西的价值相同
                result[i][j] = result[i-1][j];
                //只有当i号东西的重量小于等于目标重量j时才取i号东西
                if (WEIGHTS[i] <= j) {
                    result[i][j] = Math.max(result[i-1][j-WEIGHTS[i]]+VALUES[i], result[i][j]);
                }
            }
        }
        return result[idx][w];
    }

    public static void main(String[] args) {
        result = new int[PRODUCT_COUNTS][BAG_MAX_WEIGHT+1];
        for (int i = 0; i < PRODUCT_COUNTS; i++) {
            for (int j = 0; j < BAG_MAX_WEIGHT+1; j++) {
                result[i][j] = -1;
            }
        }
        System.out.println(search(PRODUCT_COUNTS-1, BAG_MAX_WEIGHT));
        System.out.println(search2(0, 0));
        System.out.println(search3(PRODUCT_COUNTS - 1, BAG_MAX_WEIGHT));
        System.out.println(searchDitui(PRODUCT_COUNTS - 1, BAG_MAX_WEIGHT));
    }
}

运行结果
10
10
10
10

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值