[数据结构与算法]-动态规划之背包算法终极版

序:

工作快七个年头了,现在写起算法来很是吃力,头脑生锈得很是厉害。可想而知,平时写的代码质量是有多差。看来基本功得不断打磨练习。

背包题目:

有n个物品,每个物品具有重量和价值两个属性。现有一背包最多能装重量w,求出价值最大的物品选择方案。

练习题目如图:

image.png

用动态规划思维分析

动态规划核心三要素:最优子结构,边界,状态转移公式。
最优子结构:每个物品有两个状态,被装或不被装。所以被分解程如下两个子结构


image.png

边界:当n为1时,若物品重量小于背包w,则允许物品被装。若物品重量大于背包w,则不允许被装。
状态转移公式(用i表示物品重量数组):
f(n,w) = 0;(n<1)或(n==1且i[0] > w)
f(n,w) = i[0];(n==1且i[0] < w)
f(n,w) = f(n-1, w);(n>1且i[n-1] > w)
f(n,w)= max(f(n-1,w),f(n-1,w-i[n-1]) + i[n-1]));(n>1且i[n-1] < w)

递归求解

算法思想:自顶向下,有重复计算。
求解过程如下图:

image.png

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * 背包算法
 * 题目:有n个物品,每个物品具有重量和价值两个属性。现有一背包最多能装重量w,求出价值最大的物品选择方案。
 */
public class KnapsackAlgorithm {
    /**
     * 物品类
     */
    private static class Item {
        // 重量
        private int weight;

        // 价值
        private int value;

        public Item(int weight, int value) {
            this.weight = weight;
            this.value = value;
        }

        public int getWeight() {
            return weight;
        }

        public void setWeight(int weight) {
            this.weight = weight;
        }

        public int getValue() {
            return value;
        }

        public void setValue(int value) {
            this.value = value;
        }
    }

    public static void main(String[] args) {
        int knapsackSize = 8;

        // 初始化4个物品
        Item[] items = new Item[4];
        items[0] = new Item(2, 3);
        items[1] = new Item(3, 4);
        items[2] = new Item(4, 5);
        items[3] = new Item(6, 6);

        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        KnapsackAlgorithm knapsackAlgorithm = new KnapsackAlgorithm();
        System.out.println("current time:" + simpleDateFormat.format(new Date()));
        ResultNode resultNode = knapsackAlgorithm.getMostValuableWays_recursion(knapsackSize, items, items.length);
        System.out.println("max value:" + resultNode.getSumValue());
        System.out.println("current time:" + simpleDateFormat.format(new Date()));
    }

    private static class ResultNode {
        private int index;
        private int sumValue;
        private int sumWeight;
        private ResultNode left;
        private ResultNode right;

        public int getIndex() {
            return index;
        }

        public void setIndex(int index) {
            this.index = index;
        }

        public int getSumValue() {
            return sumValue;
        }

        public void setSumValue(int sumValue) {
            this.sumValue = sumValue;
        }

        public ResultNode getLeft() {
            return left;
        }

        public void setLeft(ResultNode left) {
            this.left = left;
        }

        public ResultNode getRight() {
            return right;
        }

        public void setRight(ResultNode right) {
            this.right = right;
        }

        public int getSumWeight() {
            return sumWeight;
        }

        public void setSumWeight(int sumWeight) {
            this.sumWeight = sumWeight;
        }
    }

    /**
     * 递归求解
     * @return
     */
    private ResultNode getMostValuableWays_recursion(int knapsackSize, Item[] items, int n) {
        if (n == 1) {
            if (items[n-1].getWeight() <= knapsackSize) {
                ResultNode resultNode = new ResultNode();
                resultNode.setIndex(n-1);
                resultNode.setLeft(null);
                resultNode.setRight(null);
                resultNode.setSumValue(items[n-1].getValue());
                resultNode.setSumWeight(items[n-1].getWeight());
                return resultNode;
            } else {
                return null;
            }
        } else if (n < 1) {
            return null;
        }

        if (items[n-1].getWeight() > knapsackSize) {
            return getMostValuableWays_recursion(knapsackSize, items, n-1);
        } else {
            ResultNode leftNode = getMostValuableWays_recursion(knapsackSize, items, n-1);
            if (leftNode != null && leftNode.getSumWeight() > knapsackSize) {
                leftNode = null;
            }

            ResultNode node = getMostValuableWays_recursion(knapsackSize - items[n-1].getWeight(), items, n-1);
            ResultNode rightNode = new ResultNode();
            rightNode.setIndex(n-1);
            rightNode.setRight(node);
            rightNode.setSumValue(node != null ? node.getSumValue() + items[n - 1].getValue() : items[n - 1].getValue());
            rightNode.setSumWeight(node != null ? node.getSumWeight() + items[n - 1].getWeight() : items[n - 1].getWeight());
            if (rightNode.getSumWeight() > knapsackSize) {
                rightNode = null;
            }

            if (leftNode != null && rightNode != null) {
                if (leftNode.getSumValue() > rightNode.getSumValue()) {
                    return leftNode;
                } else if (leftNode.getSumValue() < rightNode.getSumValue()) {
                    return rightNode;
                } else {
                    ResultNode resultNode = new ResultNode();
                    resultNode.setIndex(-1);
                    resultNode.setLeft(leftNode);
                    resultNode.setRight(rightNode);
                    resultNode.setSumValue(leftNode.getSumValue());

                    return resultNode;
                }
            } else if (leftNode != null){
                return leftNode;
            } else if (rightNode != null) {
                return rightNode;
            } else {
                return null;
            }
        }
    }
}

注意:getMostValuableWays_recursion中的条件分支正好与状态转移公式的条件对应。

备忘录求解

算法思想:自顶向下,无重复计算。
此算法也是利用递归求解,与递归求解的区别在于运用了新的数据结构(比如map)来缓存算过的值。

/**
     * 备忘录求解
     */
    private ResultNode getMostValuableWays_memo(int knapsackSize, Item[] items, int n, KnapsackMap map) {
        if (n == 1) {
            if (items[n-1].getWeight() <= knapsackSize) {
                ResultNode resultNode = new ResultNode();
                resultNode.setIndex(n-1);
                resultNode.setLeft(null);
                resultNode.setRight(null);
                resultNode.setSumValue(items[n-1].getValue());
                resultNode.setSumWeight(items[n-1].getWeight());
                return resultNode;
            } else {
                return null;
            }
        } else if (n < 1) {
            return null;
        }

        if (items[n-1].getWeight() > knapsackSize) {
            if (!map.containsKey(n-1, knapsackSize)) {
                map.put(n-1, knapsackSize, getMostValuableWays_memo(knapsackSize, items, n-1, map));
            }

            return map.get(n-1, knapsackSize);
        } else {
            if (!map.containsKey(n-1, knapsackSize)) {
                map.put(n-1, knapsackSize, getMostValuableWays_memo(knapsackSize, items, n-1, map));
            }
            ResultNode leftNode = map.get(n-1, knapsackSize);
            if (leftNode != null && leftNode.getSumWeight() > knapsackSize) {
                leftNode = null;
            }

            if (!map.containsKey(n-1, knapsackSize - items[n-1].getWeight())) {
                map.put(n-1, knapsackSize - items[n-1].getWeight(), getMostValuableWays_memo(knapsackSize - items[n-1].getWeight(), items, n-1, map));
            }
            ResultNode node = map.get(n-1, knapsackSize - items[n-1].getWeight());
            ResultNode rightNode = new ResultNode();
            rightNode.setIndex(n-1);
            rightNode.setRight(node);
            rightNode.setSumValue(node != null ? node.getSumValue() + items[n - 1].getValue() : items[n - 1].getValue());
            rightNode.setSumWeight(node != null ? node.getSumWeight() + items[n - 1].getWeight() : items[n - 1].getWeight());
            if (rightNode.getSumWeight() > knapsackSize) {
                rightNode = null;
            }

            if (leftNode != null && rightNode != null) {
                if (leftNode.getSumValue() > rightNode.getSumValue()) {
                    return leftNode;
                } else if (leftNode.getSumValue() < rightNode.getSumValue()) {
                    return rightNode;
                } else {
                    ResultNode resultNode = new ResultNode();
                    resultNode.setIndex(-1);
                    resultNode.setLeft(leftNode);
                    resultNode.setRight(rightNode);
                    resultNode.setSumValue(leftNode.getSumValue());

                    return resultNode;
                }
            } else if (leftNode != null){
                return leftNode;
            } else if (rightNode != null) {
                return rightNode;
            } else {
                return null;
            }
        }
    }

    private static class Knapsack {
        private int num;
        private int weidht;

        public int getNum() {
            return num;
        }

        public void setNum(int num) {
            this.num = num;
        }

        public int getWeidht() {
            return weidht;
        }

        public void setWeidht(int weidht) {
            this.weidht = weidht;
        }

        @Override
        public int hashCode() {
            return 2;
        }

        @Override
        public boolean equals(Object obj) {
            Knapsack objLocal = (Knapsack)obj;

            return num == objLocal.getNum() && weidht == objLocal.getWeidht();
        }
    }

    private static class KnapsackMap extends HashMap<Knapsack, ResultNode> {

        public boolean containsKey(int num, int knapsack) {
            Knapsack knapsack1 = new Knapsack();
            knapsack1.setNum(num);
            knapsack1.setWeidht(knapsack);
            return super.containsKey(knapsack1);
        }

        public ResultNode get(int num, int knapsack) {
            Knapsack knapsack1 = new Knapsack();
            knapsack1.setNum(num);
            knapsack1.setWeidht(knapsack);

            return super.get(knapsack1);
        }

        public ResultNode put(int num, int knapsack, ResultNode value) {
            Knapsack knapsack1 = new Knapsack();
            knapsack1.setNum(num);
            knapsack1.setWeidht(knapsack);

            return super.put(knapsack1, value);
        }
    }

动态规划求解

算法思想:采用由低向上的思维方式,即从1个物品开始求解,直到n个物品。

求解过程:

第一步:

1kg 2kg 3kg 4kg 5kg 6kg 7kg 8kg
0 3 3 3 3 3 3 3

说明:背包能装8千克,所以表格分成8列。行数代表物品的规模。
单元格值即为f(n,w),n为物品数。若n为1,就包含第一个物品(w:2kg, v3$);n为2,就包含第一个和第二个物品(w:2kg,v:3$和w:3kg,v:4$);以此类推。

第二步:

列1 列2 列3 列4 列5 列6 列7 列8
0 3 3 3 3 3 3 3
0 3 4 4 7 7 7 7

第三步:

列1 列2 列3 列4 列5 列6 列7 列8
0 3 3 3 3 3 3 3
0 3 4 4 7 7 7 7
0 3 4 5 5 8 9 9

第四步:

列1 列2 列3 列4 列5 列6 列7 列8
0 3 3 3 3 3 3 3
0 3 4 4 7 7 7 7
0 3 4 5 5 8 9 9
0 3 4 5 5 8 9 9

注意:f(4,8)=第4行8列单元格值。

 /**
     * 动态规划求解
     */
    private int getMostValuableWays_dynamicPrograming(int knapsackSize, Item[] items, int n) {
        int[] preResult = null;
        int[] result = new int[knapsackSize];

        for(int i = 1; i <= n; i++) {
            for(int w = 1; w <= knapsackSize; w++) {
                if (i == 1) {
                    if (items[i-1].getWeight() > w) {
                        result[w-1] = 0;
                    } else {
                        result[w-1] = items[i-1].getValue();
                    }
                } else {
                    if (w-items[i-1].getWeight()-1 >=0) {
                        if (preResult[w-1] > preResult[w-items[i-1].getWeight()-1] + items[i-1].getValue()) {
                            result[w-1] = preResult[w-1];
                        } else {
                            result[w-1] = preResult[w-items[i-1].getWeight()-1] + items[i-1].getValue();
                        }
                    } else if (w-items[i-1].getWeight() == 0) {
                        result[w-1] = items[i-1].getValue();
                    } else {
                        result[w-1] = 0;
                    }
                }
            }

            preResult = Arrays.copyOf(result, 8);
        }

        return result[knapsackSize-1];
    }

算法时间复杂度和空间复杂度分析

递归:
时间复杂度:O(2^n),随物品数改变。
空间复杂度:O(2^n)

备忘录:
时间复杂度:O(2^n),随物品数改变。
空间复杂度:O(2^n)

动态规划:
时间复杂度:O(n^w),随物品数和背包重量改变。
空间复杂度:O(w)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,752评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,100评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,244评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,099评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,210评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,307评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,346评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,133评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,546评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,849评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,019评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,702评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,331评论 3 319
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,030评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,260评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,871评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,898评论 2 351

推荐阅读更多精彩内容

  • 回溯算法 回溯法:也称为试探法,它并不考虑问题规模的大小,而是从问题的最明显的最小规模开始逐步求解出可能的答案,并...
    fredal阅读 13,640评论 0 89
  • 在黑夜里孤独的行走 像是在寻找自己的路 看见前面的一点亮光 心里就充满了力量 迎着光 走过去 想起了耶稣说的话 我...
    子风乄阅读 208评论 2 1
  • 性生活太短?别乱用药 副作用不敢想 一说到壮阳药物,不少人可能性想到的就是“伟哥”了,伟哥确实能够帮助男性改善性生...
    月半水弯阅读 190评论 0 0
  • Yesterday Once More 尔峰 该要怎...
    尔峰阅读 360评论 2 3
  • 2017.08.03 - 08.10 虽然是很早约好的旅游,但是真正来的时候还是很兴奋啊,打算每天晚上写一点完整...
    Vincent1024阅读 2,092评论 1 8