动态规划之01背包和完全背包

01背包问题(注意看注释)

  1. 有N件物品和一个容量为V的背包。第i件物品的费用是c[i],价值是w[i]。每种物品仅有一件,可以选择放或不放。求解将哪些物品装入背包可使价值总和最大。
    f[i][v]表示前i件物品恰放入一个容量为v的背包可以获得的最大价值。则其状态转移方程是
    f[i][v]=max{f[i-1][v],f[i-1][v-c[i]]+w[i]}
    如果不放第i件物品,那么问题就转化为“前i-1件物品放入容量为v的背包中”,价值为f[i-1][v];如果放第i件物品,那么问题就转化为“前i-1件物品放入剩下的容量为v-c[i]的背包中”,此时能获得的最大价值就是f[i-1][v-c[i]]再加上通过放入第i件物品获得的价值w[i]。

  2. 优化空间复杂度
    for i=1..N
    for v=V..0
    f[v]=max{f[v],f[v-c[i]]+w[i]} //这里的f[v]相当于f[i-1][v],f[v-c[i]]相当于f[i-1][v-c[i]

  3. 进一步优化
    for i=1..N
    for v=V..0 //其实v不必循环到0 v=V..(V-c[i])
    f[v]=max{f[v],f[v-c[i]]+w[i]}

     初次优化后
     procedure ZeroOnePack(cost,weight)
         for v=V..cost
             f[v]=max{f[v],f[v-cost]+weight}
     for i=1..N
         ZeroOnePack(c[i],w[i])
    
     实际上,这里的下限还可以优化
     for i=1..n
         bound=max{V-sum{c[i..n]},c[i]}    //f[v] <- f[v-c[i] <- f[v-c[i]-c[i-1]] <- f[v-c[i]-c[i-1] - c[i-2]] <- f[v-sum{c[i..n] (现在真是天下文章一大抄,第一个作者有可能手误写错了,后面的博客全是错的,虽然我也参考了别人写的,但认真思考是很重要的的)。从另一个方面来考虑,当V很大的时候,根本就不需要规划,直接装。当你很有钱的时候,买东西是不是很随意
             for v=V..bound
    
public class Simple01Bag {
    /**
     * 二维数组表示
     *
     * @param c
     * @param w
     * @param v
     * @return
     */
    private static int[][] simple01Bag_1(int[] c, int[] w, int v) {
        if (c.length != w.length) throw new IllegalArgumentException();
        int[][] f = new int[c.length + 1][v + 1];

        //初始化
        for (int col = 0; col <= v; col++) {
            f[0][col] = 0;
        }
        //初始化
        for (int col = 0; col <= c.length; col++) {
            f[col][0] = 0;
        }

        for (int i = 1; i <= c.length; i++) {
            for (int j = 1; j <= v; j++) {
                if (j < c[i - 1]) f[i][j] = f[i - 1][j];    //这里是c[i-1],因为我们的数组起始索引是0
                else f[i][j] = Math.max(f[i - 1][j], f[i - 1][j - c[i - 1]] + w[i - 1]);    //这里是w[i-1],因为我们的数组起始索引是0
            }
        }

        //看看选择的是哪些
        int[] flag = new int[c.length + 1];
        for (int i = c.length; i > 0; i--) {
            if (f[i][v] > f[i - 1][v]) {
                flag[i - 1] = 1;  //这里是flag[i-1],因为我们的数组起始索引是0
                v -= c[i - 1];   //这里是c[i-1],因为我们的数组起始索引是0
            }
        }

        for (int i = 0; i < c.length; i++) {
            System.out.println(flag[i]);
        }
        return f;
    }

    /**
     * 一维数组表示  path[][]计算加入物品
     *
     * @param c
     * @param w
     * @param v
     * @return
     */
    private static int[] simple01Bag_2(int[] c, int[] w, int v) {
        if (c.length != w.length) throw new IllegalArgumentException();
        int[] f = new int[v + 1];
        int[][] path = new int[c.length + 1][v + 1];
        int bound = 0;

        //初始下限
        for (int i = 0; i < c.length; i++) {
            bound += c[i];
        }

        //  f[v] <- f[v-c[i] <- f[v-c[i]-c[i-1]] <- f[v-c[i]-c[i-1] - c[i-2]] <- f[v-sum{c[i..n]
        for (int i = 0; i < c.length; i++) {
            for (int j = v; j >= Math.max(v - bound, c[i]); j--) {
//                f[j] = Math.max(f[j], f[j - c[i]] + w[i]);
                if (f[j] < f[j - c[i]] + w[i]) {
                    f[j] = f[j - c[i]] + w[i];
                    path[i][j] = 1;
                }
            }
            bound -= c[i];
        }

        int i = c.length;
        int j = v;

        //打印出选择的物品
        while (i >= 0 && j >= 0) {
            if (path[i][j] == 1) {
                System.out.println(i + "____" + c[i]);  //  i=0..(c.length-1)
                j -= c[i];
                i--;
            } else {
                i--;
            }
        }
        return f;
    }

    //test
    public static void main(String[] args) {
        int[] c = {2, 2, 6, 5, 4};
        int[] w = {6, 3, 5, 4, 6};

        int[] f = simple01Bag_2(c, w, 10);
        for (int j = 0; j <= 10; j++) {
            System.out.print(f[j] + " - ");
        }

        System.out.println();

        int[][] ss = simple01Bag_1(c, w, 10);
        for (int i = 0; i <= c.length; i++) {
            for (int j = 0; j <= 10; j++) {
                System.out.print(ss[i][j] + " - ");
            }
            System.out.println();
        }
    }
}

完全背包问题

  1. 有N种物品和一个容量为V的背包,每种物品都有无限件可用。第i种物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
    从每种物品的角度考虑,与它相关的策略已并非取或不取两种,而是有取0件、取1件、取2件……等很多种。如果仍然按照解01背包时的思路,令f[i][v]表示前i种物品恰放入一个容量为v的背包的最大权值。仍然可以按照每种物品不同的策略写出状态转移方程,像这样:
    f[i][v]=max{f[i-1][v-kc[i]]+kw[i] | 0<=kc[i]<=v}
    求解状态f[i][v]的时间是O(v/c[i]),总的复杂度可以认为是O(V
    Σ(V/c[i])),是比较大的
  2. 简单优化
    首先将费用大于V的物品去掉,然后使用类似计数排序的做法,计算出费用相同的物品中价值最高的那个
    若两件物品i、j满足c[i]<=c[j]且w[i]>=w[j],则将物品j去掉
  3. 一维数组优化
    for i=1..N
    for v=cost..V
    f[v]=max{f[v],f[v-cost]+weight}
    下面是代码,completeBag使用一维数组,preOperate尝试丢掉一些不可能装入背包的数据。(注意看注释)
public class CompleteBag {
    public static int[] completeBag(int[] c, int[] w, int v) {
        if (c.length != w.length) throw new IllegalArgumentException();
        int[][] path = new int[c.length + 1][v + 1];   //保存添加的物品
        int[] f = new int[v + 1];
        for (int i = 0; i < c.length; i++) {
            for (int j = c[i]; j <= v; j++) {
                if (f[j] < f[j - c[i]] + w[i]) {
                    f[j] = f[j - c[i]] + w[i];
                    path[i][j] = 1;
                }
            }
        }

        int i = c.length;
        int j = v;

        //打印添加的物品,注意和简答背包一维数组的区别
        while (i >= 0 && j >= 0) {
            if (path[i][j] == 1) {
                System.out.println(i + "____" + c[i]);
                j -= c[i];
            } else {
                i--;
            }
        }
        return f;
    }

    /**
     * 可以丢掉很多数据
     * @param c
     * @param w
     * @param v
     */
    public static int[] preOperate(int[] c, int[] w, int v){
        boolean[] flag = new boolean[c.length];
        int count = 0;

        //去掉c[i] > v或者c[i] <= c[j] && w[i] >= w[j]的数据
        for(int i = 0;i < c.length ; i++){
            if(c[i] > v) {
                flag[i] = true;
                count++;
                continue;
            }
            for(int j = i + 1;j < w.length;j++){
                if(c[i] <= c[j] && w[i] >= w[j]){
                    flag[j] = true;
                    count++;
                }
            }
        }

        ArrayList<Integer> newC = new ArrayList<>();
        ArrayList<Integer> newW = new ArrayList<>();

        //一开始java代码没有想好,写的稍微有点复杂
        for(int i = 0;i < flag.length; i++){
            System.out.println(flag[i] + "   " + count);
            if(!flag[i]){
                newC.add(c[i]);
                newW.add(w[i]);
            }
        }

        int[] newc = new int[flag.length - count];
//        System.arraycopy(newC,0,newc,0,newc.length);
        int[] neww = new int[flag.length - count];
//        System.arraycopy(newW,0,neww,0,newc.length);

        for(int i = 0;i < newc.length; i++){
            newc[i] = newC.get(i);
            neww[i] = newW.get(i);
        }

//        for(int i = 0;i < newc.length; i++){
//            System.out.println(newc[i] + "  " +neww[i]);
//        }

        int[] ss = completeBag(newc,neww,v);
//        for (int i = 0; i <= v; i++) {
//            System.out.print(ss[i] + " - ");
//        }
        return ss;
    }

    public static void main(String[] args) {
        int[] c = {5, 2, 6, 5, 3};
        int[] w = {6, 3, 5, 4, 6};

        int v = 10;
//        int[] ss = completeBag(c, w, v);
//        for (int i = 0; i <= v; i++) {
//            System.out.print(ss[i] + " - ");
//        }

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

推荐阅读更多精彩内容

  • 我在进行一些互联网公司的技术笔试的时候,对于我来说最大的难题莫过于最后的那几道编程题了,这对算法和数据结构有一定程...
    柠檬乌冬面阅读 18,537评论 2 19
  • 回溯算法 回溯法:也称为试探法,它并不考虑问题规模的大小,而是从问题的最明显的最小规模开始逐步求解出可能的答案,并...
    fredal阅读 13,506评论 0 89
  • 树形动态规划,顾名思义就是树+DP,先分别回顾一下基本内容吧:动态规划:问题可以分解成若干相互联系的阶段,在每一个...
    Mr_chong阅读 1,396评论 0 2
  • 购物的时候,问一问自己是否真的需要买这个东西? 列出想要变得富有的10个原因。圈出最重要的3项。 梦想相册。收集一...
    喵小洁阅读 327评论 0 1
  • 又一个雷雨交加的夜晚,依然能看到车辆和行人穿梭在街道上,马路两边的路灯也孤独的立在那里,微弱的光在雨水的冲刷下显的...
    卢倩851001阅读 175评论 0 0