背包算法和全排列算法

最近看到一个挺有意思的算法,记录于此。

有 N 件物品和一个容量是 V 的背包。每件物品有且只有一件。
第 i 件物品的体积是 v[i] ,价值是 w[i] 。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
示例 1:
输入: N = 3, V = 4, v = [4,2,3], w = [4,2,3]
输出: 4
解释: 只选第一件物品,可使价值最大。
示例 2:
输入: N = 3, V = 5, v = [4,2,3], w = [4,2,3]
输出: 5
解释: 不选第一件物品,选择第二件和第三件物品,可使价值最大。

自己的解法

思路:列出所有组合,根据组合计算最大价格。

  1. 计算所有可能存在的组合
  • 计算前先做了功课,学习到了全排列算法的一种解法。参考地址
let func = (arr) => {
    let len = arr.length
    let res = [] // 所有排列结果
    /**
     * 【全排列算法】
     * 说明:arrange用来对arr中的元素进行排列组合,将排列好的各个结果存在新数组中
     * @param orderArr:排列好的元素
     * @param restArr:待排列元素
     */
    let arrange = (orderArr, restArr) => {
        if (orderArr.length === len) {
            res.push(orderArr)
        } else {
            restArr.forEach((item, index) => {
                let newRestArr = [...restArr]  // 复制一份待排列数组
                newRestArr.splice(index, 1) // 从待排列数组中将当前元素删除,获取到新的待排列数组
                arrange(orderArr.concat(item), newRestArr) // 递归排列:① 当前项拼接在已排列数组的后面 ②剩余数组待处理
            })
        }
    }
    arrange([], arr)
    return res
}
console.log('结果:', func(['A', 'B', 'C', 'D']))


先模拟只有两个选项['A','B']的排列过程,再模拟有三个选项['A','B','C']的排列过程。
理解了['A','B','C']的排列过程,就可以总结出规律。

  1. 以上是全排列,而我们需要的是不重复的组合排列,将newRestArr.splice(index, 1)改为截取当前往后的数组,去掉组合长度必须为数组长度的限制即可。
const packageFn = (arr) => {
    let res = []
    let getConbine = (orderArr, restArr) => {
        if (orderArr.length) {
            res.push(orderArr)
        }
        restArr.forEach((item, index) => {
            let newRestArr = [...restArr].slice(index + 1)
            getConbine(orderArr.concat(item), newRestArr)
        })
    }

    getConbine([], arr)
    return res
}
console.log(packageFn(['A', 'B', 'C', 'D']))
  1. 最后回到背包问题,计算组合方式,根据价格排序,返回最大价值的组合。
const packageFn = (ws, vs, V) => {
    // 获取所有不超出容量的组合
    let getConbine = (orderArr, restArr) => {
        let orderWeight = getWeight(orderArr)

        if (orderArr.length) {
            res.push(orderArr)
        }

        restArr.forEach((item, i) => {
            let newRestArr = [...restArr].slice(i + 1)

            if (orderWeight + item.weight <= V) { // 不超出总重量才会循环下去
                getConbine(orderArr.concat(item), newRestArr)
            }
        });
    }

    // 计算重量
    const getWeight = (arr) => {
        return arr.reduce((total, cur) => total + cur.weight, 0)
    }

    // 计算价格
    const getPrice = (arr) => {
        return arr.reduce((total, cur) => total + cur.price, 0)
    }

    let res = []
    let wvArr = ws.map((item, index) => {
        return {
            index: index,
            weight: item,
            price: vs[index]
        }
    });

    getConbine([], wvArr) // 获取所有不超出容量的组合
    // console.log(res)

    res.sort((a, b) => { // 按价格排序
        return getPrice(b) - getPrice(a)
    })

    return res[0]
}

var ws = [2, 2, 6, 5, 4], vs = [6, 3, 5, 4, 6], V = 10
console.log(packageFn(ws, vs, V))

递归解法

解法来源于https://segmentfault.com/a/1190000012829866,具体看这篇文章吧,没有第一步的解析,也没法理解以下的代码。

function knapsack(n, W, weights, values, selected) {
    if (n == 0 || W == 0) {
        //当物品数量为0,或者背包容量为0时,最优解为0
        return 0;
    } else {
        //从当前所剩物品的最后一个物品开始向前,逐个判断是否要添加到背包中
        for (var i = n - 1; i >= 0; i--) {
            //如果当前要判断的物品重量大于背包当前所剩的容量,那么就不选择这个物品
            //在这种情况的最优解为f(n-1,C)
            if (weights[i] > W) {
                return knapsack(n - 1, W, weights, values, selected);
            } else {
                var a = knapsack(n - 1, W, weights, values, selected); //不选择物品i的情况下的最优解
                var b = values[i] + knapsack(n - 1, W - weights[i], weights, values, selected); //选择物品i的情况下的最优解
                //返回选择物品i和不选择物品i中最优解大的一个
                if (a > b) {
                    selected[i] = 0; //这种情况下表示物品i未被选取
                    return a;
                } else {
                    selected[i] = 1; //物品i被选取
                    return b;
                }
            }
        }
    }
}        
var selected = [], ws = [2,2,6,5,4], vs = [6,3,5,4,6]
var b = knapsack( 5, 10, ws, vs, selected)
console.log(b) //15
selected.forEach(function(el,i){
    if(el){
        console.log("选择了物品"+i+ " 其重量为"+ ws[i]+" 其价值为"+vs[i])
    }
})
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342

推荐阅读更多精彩内容