6.00.2x学习笔记Week5 Part1
Optimization Problem
Optimization包含两部分
-
An objective function that is to be maximized or minimized.
例如寻找Boston到Istanbul最便宜的机票
-
A set of constraints
比较典型的应用
- Shortest path
- Traveling salesman: 销售员要走过一些城市,如何最大程度避免重复
- Bin Packing:如何把货物装入集装箱
- Sequence alignment: two genetic strings. find a way to line them up to maximize the overlap.
- Knapsack
Knapsack 背包问题
import pylab
class Item(object):
def __init__(self, n, v, w):
self.name = n
self.value = float(v)
self.weight = float(w)
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
def buildItems():
names = ['clock', 'painting', 'radio', 'vase', 'book', 'computer']
vals = [175,90,20,50,10,200]
weights = [10,9,4,2,1,20]
Items = []
for i in range(len(vals)):
Items.append(Item(names[i], vals[i], weights[i]))
return Items
建立Item Class
, 把所有的item
组成一个Items List
。
贪心算法
def greedy(Items, maxWeight, keyFcn):
assert type(Items) == list and maxWeight >= 0
ItemsCopy = sorted(Items, key=keyFcn, reverse = True)
result = []
totalVal = 0.0
totalWeight = 0.0
i = 0
while totalWeight < maxWeight and i < len(Items):
if (totalWeight + ItemsCopy[i].getWeight()) <= maxWeight:
result.append((ItemsCopy[i]))
totalWeight += ItemsCopy[i].getWeight()
totalVal += ItemsCopy[i].getValue()
i += 1
return (result, totalVal)
三种不同的指标:
def value(item):
return item.getValue()
def weightInverse(item):
return 1.0/item.getWeight()
def density(item):
return item.getValue()/item.getWeight()
取任意一个指标得出的结果:
def testGreedy(Items, constraint, getKey):
taken, val = greedy(Items, constraint, getKey)
print ('Total value of items taken = ' + str(val))
for item in taken:
print ' ', item
把三种综合在一块:
def testGreedys(maxWeight = 20):
Items = buildItems()
print('Items to choose from:')
for item in Items:
print ' ', item
print 'Use greedy by value to fill a knapsack of size', maxWeight
testGreedy(Items, maxWeight, value)
print 'Use greedy by weight to fill a knapsack of size', maxWeight
testGreedy(Items, maxWeight, weightInverse)
print 'Use greedy by density to fill a knapsack of size', maxWeight
testGreedy(Items, maxWeight, density)
testGreedys()
得到:
``` Items to choose from:
<clock, 175.0, 10.0> <painting, 90.0, 9.0> <radio, 20.0, 4.0> <vase, 50.0, 2.0> <book, 10.0, 1.0> <computer, 200.0, 20.0> Use greedy by value to fill a knapsack of size 20 Total value of items taken = 200.0 <computer, 200.0, 20.0> Use greedy by weight to fill a knapsack of size 20 Total value of items taken = 170.0 <book, 10.0, 1.0> <vase, 50.0, 2.0> <radio, 20.0, 4.0> <painting, 90.0, 9.0> Use greedy by density to fill a knapsack of size 20 Total value of items taken = 255.0 <vase, 50.0, 2.0> <clock, 175.0, 10.0> <book, 10.0, 1.0> <radio, 20.0, 4.0>
但这只是按照标准排序,并不是最佳方案。当然,我们可以用brutal force的方法来寻找最佳答案。把所有可能的组合都列出来,取符合限制条件的最大值。
首先写了一个helper function,把数字n转化为小于特定数字的二进制数。
```python
def dToB(n, numDigits):
"""requires: n is a natural number less than 2**numDigits
returns a binary string of length numDigits representing the
the decimal number n."""
assert type(n)==int and type(numDigits)==int and n >=0 and n < 2**numDigits
bStr = ''
while n > 0:
bStr = str(n % 2) + bStr
n = n//2
while numDigits - len(bStr) > 0:
bStr = '0' + bStr
return bStr
然后 Generate a list of lists representing the power set of Items。
def genPset(Items):
"""Generate a list of lists representing the power set of Items"""
numSubsets = 2**len(Items)
templates = []
for i in range(numSubsets):
templates.append(dToB(i, len(Items)))
pset = []
for t in templates:
elem = []
for j in range(len(t)):
if t[j] == '1':
elem.append(Items[j])
pset.append(elem)
return pset
然后,从中选择best
def chooseBest(pset, constraint, 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 <= constraint and ItemsVal > bestVal:
bestVal = ItemsVal
bestSet = Items
return (bestSet, bestVal)
def testBest():
Items = buildItems()
pset = genPset(Items)
taken, val = chooseBest(pset, 20, Item.getValue, Item.getWeight)
print ('Total value of items taken = ' + str(val))
for item in taken:
print ' ', item
testBest()
得到如下答案:
``` Total value of items taken = 275.0
<clock, 175.0, 10.0> <painting, 90.0, 9.0> <book, 10.0, 1.0>
当然,更好的办法是decision tree:
```python
def maxVal(toConsider, avail):
if toConsider == [] or avail == 0:
result = (0, ())
elif toConsider[0].getWeight() > avail:
result = maxVal(toConsider[1:], avail)
else:
nextItem = toConsider[0]
#Explore left branch
withVal, withToTake = maxVal(toConsider[1:],
avail - nextItem.getWeight())
withVal += nextItem.getValue()
#Explore right branch
withoutVal, withoutToTake = maxVal(toConsider[1:], avail)
#Choose better branch
if withVal > withoutVal:
result = (withVal, withToTake + (nextItem,))
else:
result = (withoutVal, withoutToTake)
return result
def smallTest():
Items = buildItems()
val, taken = maxVal(Items, 20)
for item in taken:
print(item)
print ('Total value of items taken = ' + str(val))
smallTest()
鉴于背包问题本质上的复杂性是指数型的,所以我们这里介绍一下另外一种可以saving redundant work的方法。思路主要是把partial solution保留下来,不用每次用到的时候都重新算。
最简单的Fibonacci算法:
def fib(x):
assert type(x) == int and x >= 0
if x == 0 or x == 1:
return 1
else:
return fib(x-1) + fib(x-2)
def testFib(n):
assert type(n) == int and n >= 0
for i in range(n):
print ('fib of', i, '=', fib(i))
会越算越慢。如果把中间计算结果用dict存起来,就会快很多。Memoization
def fastFib(x, memo):
assert type(x) == int and x >= 0 and type(memo) == dict
if x == 0 or x == 1:
return 1
if x in memo:
return memo[x]
result = fastFib(x-1, memo) + fastFib(x-2, memo)
memo[x] = result
return result
def testFastFib(n):
assert type(n) == int and n >= 0
for i in range(n):
print ('fib of', i, '=', fastFib(i, {}))
Read more: