[0-1背包问题]有一个背包,背包容量是M=150kg。有7个物品,物品不可以分割成任意大小。(这句很重要)
要求尽可能让装入背包中的物品总价值最大,但不能超过总容量。
物品 A B C D E F G
重量 35kg 30kg 6kg 50kg 40kg 10kg 25kg
价值 10 40 30 50 35 40 30
要想得到最优的结果,最容易想到的就是穷举法,那么首先用穷举法来处理这个问题。
首先就是要看穷举一共有多少种拿法, 27 种拿法,也就是说一共有128种方法,这里面一定有最优的,我们只要计算出128种拿法的价值,就一定能选出最优的拿法,当然要排除掉超过总容量的集合。
为了专业一点,首先要定义一个类Item来将问题转化为计算机能够理解的问题。
# item have three attribute: name,weight,value
class Item(object):
def __init__(self,n,v,w):
self.name=n
self.weight=float(w)
self.value=float(v)
def getName(self):
return self.name
def getValue(self):
return self.value
def getWeight(self):
return self.weight
def __str__(self):
result = ' < '+self.name+' , '+str(self.value)+' , '+str(self.weight) + '>'
return result
下面的这段算法提供了整个解空间,她的返回值就是整个 27 个集合
#get the binary representation of a num n
def getBinaryRep(n,numDigitals):
result=''
while n>0:
result=str(n%2)+result
n=n//2
if len(result)>numDigitals:
raise ValueError("not enough digits")
for i in range(numDigitals - len(result)):
result = '0'+result
return result
def genPowerSet(L):
powerset=[]
for i in range(2**len(L)):
binstr = getBinaryRep(i,len(L))
subset = []
for j in range(len(L)):
if binstr[j]=='1':
subset.append(L[j])
powerset.append(subset)
return powerset
这段代码需要详细的解释一下。
getBinaryRep
就是获取一个数的指定位数的二进制表示。不足的位数补0,运行这个函数的结果如下所示:
print getBinaryRep(7,7)
0000111
genPowerSet
这个函数可以获取指定输入集合的所有子集合的表示。
原理如下:
我们可以把输入集合当做一个元素的列表,任意一个子集合看成是一个只包含了0,1的字符串,0表示不包含指定元素,1表示包含指定元素,那么全0就表示空集,全1就表示包括所有元素的集合(输入集合)。因此0– 27 所有的数的2进制就代表了输入集合的所有子集,也就是找到了问题的全部子空间。
函数的运行结果示例:
L = ['A','B','C']
result = genPowerSet(L)
for i in range(len(result)):
print result[i]
[]
['C']
['B']
['B', 'C']
['A']
['A', 'C']
['A', 'B']
['A', 'B', 'C']
准备工作做完了,下面来利用穷举法解决一下0/1背包问题
from Item import Item
import genPowerSet
def buildItem():
names=['A','B','C','D','E','F','G']
vals = [35,30,6,50,40,10,25]
weights=[10,40,30,50,35,40,30]
Items=[]
for i in range(len(names)):
Items.append(Item(names[i],vals[i],weights[i]))
return Items
# Brute-force method
def chooseBest(pset,maxWeight,getVal,getWeight):
bestVal = 0.0
bestSet=None
for items in pset:
itemsVal=0.0
itemsWeight=0.0
for item in items:
itemsVal +=getVal(item)
itemsWeight +=getWeight(item)
if itemsWeight <=maxWeight and itemsVal >bestVal:
bestVal = itemsVal
bestSet = items
return (bestSet, bestVal)
def testBest(maxWeight=150):
items = buildItem()
pset = genPowerSet.genPowerSet(items)
taken,val = chooseBest(pset,maxWeight,Item.getValue,Item.getWeight)
print 'Total value of items taken = ',val
for item in taken:
print item
start1 = time.clock()
testBest()
end1=time.clock()
print "runing time is %f" % (end1-start1)
Total value of items taken = 155.0
< A , 35.0 , 10.0>
< B , 30.0 , 40.0>
< D , 50.0 , 50.0>
< E , 40.0 , 35.0>
runing time is 0.001193
从实验结果上看,还是很快的,穷举法很完美啊。但是穷举的复杂度可是指数级的,当数据量比较大时就要歇了,举个例子,比如说集合的元素数目为20的时候,仅仅函数genPowerSet
就要花费14.1231262126s,这就是指数的可怕,如果是26的话,就呵呵了(反正我是死机了)。
所以穷举法对于数据量小的情形是很好用的,但是数据量稍微大一些,就无路可走了。