0/1背包问题之穷举解法

[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的话,就呵呵了(反正我是死机了)。

所以穷举法对于数据量小的情形是很好用的,但是数据量稍微大一些,就无路可走了。

评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值