背包算法

yuyu888 于 2022-01-04 发布

背包问题

假设山洞里共有a, b, c, d, e这5件宝物(不是5种宝物),它们的重量分别是2, 3, 4, 4, 6,它们的价值分别是5, 4, 6, 3, 6 现在给你个承重为10的背包, 怎么装背包,可以才能带走最多的财富。

问题分析

这个问题俗称01背包,由于这5件物品都是唯一的,所以对于每个物品,我们对他的操作只有 拿了 or 没拿;可以用0表示 没放入包里;用1表示放进了包里

比如我们把 a,b,c 放进了背包, 那么可以表示为 1 1 1 0 0 转成10进制为28; 把a,c,e放进了背包可以认为是1 0 1 0 1转十进制为21, 全放进去为1 1 1 1 1 转10进制为63

所以我们只要遍历每一种可能, 然后验证这种可能的组合质量是不是小于10, 然后在进一步计算出该组合的价值,然后跟其他符合条件的组合产生的价值比较,取最大的即可

代码:

package main

import "fmt"

func main() {

	arrWeight := [5]int{2, 3, 4, 4, 6} // 物品质量
	arrValue := [5]int{5, 4, 6, 3, 6}  // 对应价值
	bagCapacity := 10                  //背包容量

	maxValue := 0
	n := 1<<len(arrWeight) - 1 //复杂度2^n-1

	for i := 1; i < n; i++ {
		weight := getWeight(i, arrWeight[:])
		if weight < bagCapacity {
			thisValue := getValue(i, arrValue[:])
			if maxValue < thisValue {
				maxValue = thisValue
			}
		}
	}
	fmt.Println(maxValue)
}

func getWeight(bitmap int, arrWeight []int) int {
	i := 1
	weight := 0
	len := len(arrWeight)
	for {
		if bitmap == 0 {
			break
		}
		if bitmap&1 == 1 {
			j := len - i
			weight = weight + arrWeight[j]
		}
		bitmap = bitmap >> 1
		i++
	}
	return weight
}

func getValue(bitmap int, arrValue []int) int {
	i := 1
	value := 0
	len := len(arrValue)
	for {
		if bitmap == 0 {
			break
		}
		if bitmap&1 == 1 {
			j := len - i
			value += arrValue[j]
		}
		bitmap = bitmap >> 1
		i++
	}
	return value
}

进一步分析

如果我们按照上述思路,穷举暴力解决, 算法复杂度为2n-1

仔细观察, 如果放入了D,E 则表达式为 0 0 0 1 1; 就再也放不了别的物品了, 穷举的时候1 0 0 1 1和1 1 0 1 1 等情况都是没有意义的计算, 很明显算法有更进一步的优化空间

按照这个思路, 我们可以依次放入物品,只要总重量小于背包承载重量, 就放入下一个物品,当放入下一个物品质量超出了背包所能承受的重量,那么任何该组合下其他种可能都不用再尝试,接下来,放弃该物品,放入下一个物品, 直到尝试到最后一个物品;

然后拿掉第一个放入的物品,在剩余的四个物品中(产生新的组合), 再次尝试上述操作, 直到剩余物品组合个数为0

package main

import "fmt"

func main() {
	arrWeight := [5]int{2, 3, 4, 4, 6} // 物品质量
	arrValue := [5]int{5, 4, 6, 3, 6}  // 对应价值
	bagCapacity := 10                  //背包容量

	maxValue := knapsack(arrWeight[:], arrValue[:], bagCapacity)
	fmt.Println(maxValue)

}

// func knapsack(arrWeight []int, arrValue []int, bagCapacity int) int {
// 	if bagCapacity == 0 {
// 		return 0
// 	}
// 	maxValue := 0
// 	n := len(arrWeight)
// 	if n > 0 {
// 		fmt.Println(666)
// 		if arrWeight[0] <= bagCapacity {
// 			maxValue += arrValue[0]
// 			if n > 1 {
// 				maxValue += knapsack(arrWeight[1:], arrValue[1:], bagCapacity-arrWeight[0])
// 			}
// 		}
// 		if n > 1 {
// 			newMaxvalue := knapsack(arrWeight[1:], arrValue[1:], bagCapacity)
// 			if newMaxvalue > maxValue {
// 				maxValue = newMaxvalue
// 			}
// 		}
// 	}
// 	return maxValue
// }

func knapsack(arrWeight []int, arrValue []int, bagCapacity int) int {
	weight := 0
	maxValue := 0
	n := len(arrWeight)
	if n > 0 {
		for i := 0; i < n; i++ {
			newWeight := weight + arrWeight[i]
			if newWeight < bagCapacity {
				weight += arrWeight[i]
				maxValue += arrValue[i]
			} else {
				if i+1 < n {
					maxValue += knapsack(arrWeight[i+1:], arrValue[i+1:], bagCapacity-weight)
					break
				}

			}
		}
		newMaxvalue := knapsack(arrWeight[1:], arrValue[1:], bagCapacity)
		if newMaxvalue > maxValue {
			maxValue = newMaxvalue
		}
	}

	return maxValue
}

上面的解决方案是以质量为参考,也有按价值, 以及按价值/质量比,排序后再进行演算

其他背包模型

本文中仅探讨了 01背包; 还有其他背包模型

完全背包问题

现在有A,B,C,D,E…..等 N种物品, 每种物品都有n个;质量分别是Aw,Bw,Cw,Dw,Ew….; 价值为Av,Bv, Cv, Dv, Ev……; 现在给出一个承载质量为M的背包,如何装能让包中的物品价值最大化

背包求解

现在有物品A,B,C,D,E种物品, 质量分别为Aw,Bw,Cw,Dw,Ew; 现在有一个包,包里装有上述物品,每件物品最多一个,总质量为M;在不打开背包的情况下,求背包里可能装的都是哪些物品