【算法】动态规划-背包算法

题目:

菜地里有四件蔬菜: 
土豆[重量:2, 价值:300]
红薯[重量:1, 价值:150]
萝卜[重量:3, 价值:200]
青菜[重量:1, 价值:200]

兔子有个容量为4(最大重量4)的背包,怎么装能够实现背包价值最大?

建模

物品对象:

/**
 * 物品对象
 * 所有物品需要继承此类
 */
class Item {
    // 物品名称
    private String name;
    // 物品重量
    private int itemWeight;
    // 物品价值
    private int itemValue;

    public int getItemWeight() {
        return itemWeight;
    }

    public void setItemWeight(int itemWeight) {
        this.itemWeight = itemWeight;
    }

    public int getItemValue() {
        return itemValue;
    }

    public void setItemValue(int itemValue) {
        this.itemValue = itemValue;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Item(int itemWeight, int itemValue) {
        this.itemWeight = itemWeight;
        this.itemValue = itemValue;
    }

    public Item(String name, int itemWeight, int itemValue) {
        this.name = name;
        this.itemWeight = itemWeight;
        this.itemValue = itemValue;
    }
}

背包对象:

/**
 * 背包建模
 * @author zx
 * @date   2021年12月20日
 */
public class Package {

    /**
     * 背包容量
     */
    private int maxWeight = 0;
    /**
     * 背包内物品的当前价值
     */
    private int currentValue = 0;
    /**
     * 背包内物品的当前重量
     */
    private int currentWeight = 0;


    /**
     * 背包内所装物品的集合
     */
    private List<Item> items = new ArrayList<>();

    public Package addItem(Item item){
        if(collectionIsEmpty(items)){

            Package oneItemPackage = new Package();
            oneItemPackage.setMaxWeight(item.getItemWeight());
            oneItemPackage.setCurrentValue(item.getItemValue());
            oneItemPackage.setCurrentWeight(item.getItemWeight());

            List<Item> itemShadow = new ArrayList<>();
            itemShadow.add(item);
            oneItemPackage.setItems(itemShadow);
            return oneItemPackage;
        }else{
            Package manyItemPackage = new Package();
            manyItemPackage.setMaxWeight(this.maxWeight += item.getItemWeight());
            manyItemPackage.setCurrentValue(this.currentValue += item.getItemValue());
            manyItemPackage.setCurrentWeight(this.currentWeight += item.getItemWeight());

            List<Item> itemShadow = new ArrayList<>(items);
            itemShadow.add(item);
            manyItemPackage.setItems(itemShadow);
            return manyItemPackage;
        }
    }

    public Package() {}

    public Package(int weight, int currentValue) {
        this.maxWeight = weight;
        this.currentValue = currentValue;
    }

    public int getMaxWeight() {
        return maxWeight;
    }

    public void setMaxWeight(int maxWeight) {
        this.maxWeight = maxWeight;
    }

    public int getCurrentWeight() {
        return currentWeight;
    }

    public void setCurrentWeight(int currentWeight) {
        this.currentWeight = currentWeight;
    }

    public int getCurrentValue() {
        return currentValue;
    }

    public void setCurrentValue(int currentValue) {
        this.currentValue = currentValue;
    }

    public List<Item> getItems() {
        return items;
    }

    public void setItems(List<Item> items) {
        this.items = items;
    }

    public boolean collectionIsEmpty(@SuppressWarnings("rawtypes") Collection collection){
        return collection == null || collection.size() == 0;
    }
}

算法分析

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

/**
 * 动态规划 - 基本背包
 * @author zx
 * @date   2021年12月30日
 */
public class BagUtils {

    /**
     * 问题:菜地里有四棵蔬菜: {土豆[重量:2, 价值:300],红薯[重量:1, 价值:150],萝卜[重量:3, 价值:200],青菜[重量:1, 价值:200]}
     * 兔子有个容量为4(最大重量4)的背包,怎么装能够实现背包价值最大?
     *
     * 思路
     * x容量\y物品         包容量:1       包容量:2     包容量:3     包容量:4
     *        土豆         装不下,0       300         300        300
     *        红薯         150           300         450        ?
     *        萝卜
     *        青菜
     * 对于每一个格子下的当前物品,只存在装与不装的情况。能装下则背包价值更新,装不下则保持上一列的总价值
     * 不装新物品:         MaxValue =  当前容量上一行当前容量的价值。 (红薯上一行是土豆。在包容量:4的列,不装红薯,当前包内物品最大价值为土豆 = 300)
     * 装新物品【能装下】:  MaxValue =   当前物品的价值 + (背包总容量 - 当前物品的容量)能装的最大价值。
     *
     * (在包容量:4的列,当前物品的价值 = 红薯的价值 150-重量:1 + 剩余背包容量3时,能装下的除红薯外最大价值的物品。
     * 剩余背包容量3时,能装下的除红薯外最大价值的物品等于什么呢?
     * 这个在之前已经计算过了,在仅有土豆红薯情况下,上表土豆行在容量3时最大能装价值300的物品。所以在包容量:4的列,当前物品的价值 = 红薯的价值 150+土豆行在容量3时最大能装价值300 = 450.
     * 总结:状态转移公式:
     * Max[i,packageWeight] = MAX( 不装当前物品:Max[i-1,packageWeight]上一行本列背包所装物品的最大价值。 VS  装当前物品:Max[i-1, packageWeight - 当前item重量]。 )
     */
    public static void main(String[] args) {

        // 物品列表
        List<Item> items = new ArrayList<>();
        Item item1 = new Item("土豆",2, 300);
        Item item2 = new Item("红薯",1, 150);
        Item item3 = new Item("萝卜",3, 200);
        Item item4 = new Item("青菜",1, 200);
        items.add(item1);
        items.add(item2);
        items.add(item3);
        items.add(item4);

        /*
         * List<List<Package>> calMaxValueCache 用于缓存计算过的节点数据。
         * index0 存储第一行【第一个物品分别在背包容量1、2、3、4....n】的最大价值 计算数据
         * eg:                 背包容量0   背包容量1   背包容量2   背包容量3   背包容量4
         * 土豆[重量2,价值300]    0 	      0	         300      	300	      300
         * 红薯[重量1,价值150]    0        150        300        450       450
         * 则:
         * calMaxValueCache index0 中存储的数据为 :[0,	 0,   300, 300, 300]
         * calMaxValueCache index1 中存储的数据为 :[0,	 150, 300, 450, 450]
         */
        List<List<Package>> calMaxValueCache = new ArrayList<>();

        //双层循环代表:轮询每个物品在 背包容量 1、2、3、4...n 下的所有情况,itemIndex对应行
        for (int itemIndex = 0; itemIndex < items.size(); itemIndex++) {
            List<Package> x1yn = new ArrayList<>();
            // packageWeight 对应列
            for (int packageWeight = 0; packageWeight <= 4; packageWeight++) {
                //第n个物品,在背包容量为 0、1、2、3、4 下,calMaxValueCache index0 中存储的数据为 :[0,	 0,   300, 300, 300]
                Item item = items.get(itemIndex);
                int itemValue = item.getItemValue();
                int itemWeight = item.getItemWeight();
                // 第一个物品第一行,对应背包容量1234,不需要特殊计算,只需要看能否装下当前物品即可
                if(itemIndex == 0){
                    Package oneItemPackage = new Package();
                    if(itemWeight <= packageWeight){
                        // 装的下才会更新背包最大价值
                        oneItemPackage = oneItemPackage.addItem(item);
                    }
                    // 存储计算过的结果数据
                    x1yn.add(oneItemPackage);
                    continue;
                }

                // 第二行开始遵循以下规律
                // Max[i,packageWeight] = MAX( 不装当前物品:Max[i-1,packageWeight], 装当前物品:Max[i-1, packageWeight - 当前item重量] )

                /*
                 * 上一整行【上个物品】计算好的价值信息
                 * calMaxValueCache index0 中存储的数据为 :[0,	 0,   300, 300, 300]
                 * calMaxValueCache index1 中存储的数据为 :[0,	 150, 300, 450, 450]
                 */
                List<Package> xny_1Cache = calMaxValueCache.get(itemIndex -1);

                // 【不装本物品】,当前最大背包物品价值 = 上一行本列对应背包所装的物品价值 = 不装当前物品:Max[i-1,packageWeight]
                Package unAddThisItemPackage = new Package();
                if(packageWeight != 0){
                    unAddThisItemPackage = xny_1Cache.get(packageWeight);
                }
                int unAddThisItemPackageValue = unAddThisItemPackage.getCurrentValue();

                // 【装当前物品】,不考虑装不下,装不下会取【不装本物品】的情况
                Package addThisItemPackage = new Package();
                if(itemWeight <= packageWeight){
                    // 将当前物品放入背包,此时背包会装满或剩余空间
                    addThisItemPackage = addThisItemPackage.addItem(item);
                    // 计算剩余空间还能装下的最大的重量的物品(除本物品外),在之前已经计算过了,所以此处直接查找对应背包容量列,最大价值的格子。
                    int leftRoom = packageWeight  - itemWeight;
                    if(leftRoom > 0){
                        // 查询剩余空间在之前计算过的,leftRoom大小空间能够装的最大的物品
                        List<Package> leftRoomCol = new ArrayList<>();
                        for (int itemIndexExceptSelf = 1; itemIndexExceptSelf <= calMaxValueCache.size(); itemIndexExceptSelf++) {
                            // n-1列(当前物品只有一件,不能将自己多次装入)
                            List<Package> packages = calMaxValueCache.get(itemIndexExceptSelf - 1);
                            // 背包剩余空间leftRoom 代表的是背包容量所在的列「下标」
                            Package aPackage1 = packages.get(leftRoom);
                            leftRoomCol.add(aPackage1);
                        }
                        if(leftRoomCol.size() > 0){
                            // calMaxValueCache 中上一列中,除本物品外,价值最高的物品所在的背包
                            Package maxValuePackage = leftRoomCol.stream().max(
                                Comparator.comparingInt(Package::getCurrentValue)).get();
                            // 将背包物品装进 【装当前物品】 情况中剩余的背包空间中
                            List<Item> maxValuePackageItems = maxValuePackage.getItems();
                            for (Item maxValuePackageItem : maxValuePackageItems) {
                                addThisItemPackage = addThisItemPackage.addItem(maxValuePackageItem);
                            }
                        }
                    }

                }
                // 不装当前物品:Max[i-1,packageWeight] VS 装当前物品:Max[i-1, packageWeight - 当前item重量] ,取价值更高的情况
                int addThisItemPackageValue = addThisItemPackage.getCurrentValue();
                if(addThisItemPackageValue >= unAddThisItemPackageValue){
                    x1yn.add(addThisItemPackage);
                }else {
                    x1yn.add(unAddThisItemPackage);
                }
            }
            // 缓存本列计算完成的值
            calMaxValueCache.add(x1yn);
        }

        // 表格形式打印所有情况
        for (List<Package> packages : calMaxValueCache) {
            for (Package aPackage : packages) {
                System.out.print(aPackage.getCurrentValue()+"\t");
            }
            System.out.println();
        }
    }

}

结果:


背包容量  0    1     2    3      4
土豆      0  	0	300	  300	300	
红薯      0	  150	300	  450	450	
萝卜      0	  150	300   450	450	
青菜      0	  200	350	  500	650

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值