第12 章 背包与图的最优化问题

最优化问题提供了一种结构化的方法,可以解决很多计算问题。解决问题时,如果涉及求最
大、最小、最多、最少、最快、最低价格等情况,那么你就非常有可能将这个问题转换为一个典
型的最优化问题,从而使用已知的计算方法进行解决。
最优化问题通常包括两部分。
 目标函数:需要最大化或最小化的值。例如,波士顿和伊斯坦布尔之间的飞机票价。
 约束条件集合(可以为空):必须满足的条件集合。例如旅行时间的上界。
在本章中,我们会介绍最优化问题的概念,并给出几个例子,当然,还会给出解决这些问题
的一些简单算法。在第13章中,我们会详细讨论一类重要最优化问题的有效解决方法。
本章要点如下:
 很多具有重要现实意义的问题都可以表述为一种简单的形式,并顺理成章地使用计算方
法来解决;
 将一个貌似新鲜的问题归结为我们熟知问题的一个实例,就可以使用已有的方案解决这
个问题;
 很多其他问题都可以归结为背包问题和图的最优化问题;
 穷举法提供了一种搜索最优解的简单方法,但在计算上经常是不可行的;
 贪婪算法非常实用,经常可以为最优化问题找出相当好的解,但不一定是最优解。
和往常一样,我们会补充一些计算思维方面的知识,有些是关于Python的,有些则关于编程技巧。
12.1 背包问题
当入室窃贼并不容易。除了要解决那些显而易见的问题(确认家里没人、开锁、绕过警报器、
克服负罪感等),窃贼还要确定偷哪些东西。问题是,多数家庭中有价值的东西都不是普通窃贼
能全部带走的。窃贼该怎么办呢?他需要找出一组能够带走的价值最高的东西。
假设窃贼有一个背包①,最多能装20磅赃物,他闯入一户人家,发现图12-1中的物品。很显然,他不能把所有物品都装进背包,所以必须确定拿走哪些物品,留下哪些物品。

图12-1 物品表

12.1.1 贪婪算法
对于这个问题,找出近似解的最简单方法就是贪婪算法。窃贼会首先选择最好的物品,然后
是次好的,这样继续下去,直到将背包装满。当然,在此之前,窃贼必须确定什么是“最好”的。
最好的物品是价值最高的,重量最轻的?还是具有最高价值/重量比值的呢?如果选择价值最高
的物品,就应该只带电脑离开,这样可以得到200美元。如果选择重量最轻的,那么应该依次带
走书、花瓶、收音机和油画,一共价值170美元。最后,如果确定“最好”的含义是价值/重量比
值最高,那么应当首先拿走花瓶和钟。然后有三种物品的价值/重量比值都是10,但背包里只能
放下书了。拿走书之后,他还可以拿走收音机。这样,所有赃物的价值是255美元。
对于这个数据集,尽管按照密度(价值与重量的比值)进行贪婪恰好得到了最优结果,但相
对于按照重量或价值进行贪婪的算法来说,我们不能保证按照密度贪婪的算法一直能得到更好的
解。更普遍地说,对于这种背包问题,无法确保使用贪婪算法找出的解是最优解。①稍后会更详
细地讨论这个问题。
图12-2~图12-4中的代码实现了所有3种贪婪算法。在图12-2中,我们定义了Item类。每个Item
对象都有name、value和weight属性。我们还定义了3个函数,都可以作为greedy函数实现中参
数keyFunction的值。参见图12-3。
 

class Item(object):
def __init__(self, n, v, w):
self.name = n
self.value = v
self.weight = 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 value(item):
return item.getValue()
def weightInverse(item):
return 1.0/item.getWeight()
def density(item):
return item.getValue()/item.getWeight()

图12-2 Item类

def greedy(items, maxWeight, keyFunction):
"""假设Items是列表,maxWeight >= 0
keyFunctions将物品元素映射为数值"""
itemsCopy = sorted(items, key=keyFunction, reverse = True)
result = []
totalValue, totalWeight = 0.0, 0.0
for i in range(len(itemsCopy)):
if (totalWeight + itemsCopy[i].getWeight()) <= maxWeight:
result.append(itemsCopy[i])
totalWeight += itemsCopy[i].getWeight()
totalValue += itemsCopy[i].getValue()
return (result, totalValue)

图12-3 贪婪算法的实现

通过引入参数keyFunction,我们使greedy函数在处理一个物品列表时,完全不用考虑列表
中元素的排列顺序,只要keyFunction定义了items中元素的排序规则即可。可以使用定义在
keyFunction中的排序规则对items进行排序,并生成一个和items具有同样元素的排好序的列
表。使用Python内置的sorted函数进行排序。(使用sorted而不用sort的原因是,我们想生成一
个新列表,而不是修改作为函数参数的列表。)我们还使用reverse参数表示按照从大到小的方式
对列表进行排序(排序规则在keyFunction中定义)。

那么greedy算法的效率如何呢?我们需要考虑两件事情:内置函数sorted的时间复杂度,
以及greedy函数内部for循环执行的次数。循环迭代次数由items中的元素数量决定,也就是说,
复杂度为O(n),这里的n是items的长度。然而,在最差情形下,Python内置排序函数的复杂度大
概是O(nlog(n)),这里的n是待排序列表的长度①。因此,贪婪算法的时间复杂度是O(nlog(n))。
图12-4中的代码建立了一个物品列表,并使用3种列表排序方式对greedy函数进行了测试。

def buildItems():
names = ['clock','painting','radio','vase','book','computer']
values = [175,90,20,50,10,200]
weights = [10,9,4,2,1,20]
Items = []
for i in range(len(values)):
Items.append(Item(names[i], values[i], weights[i]))
return Items
def testGreedy(items, maxWeight, keyFunction):
taken, val = greedy(items, maxWeight, keyFunction)
print('Total value of items taken is', val)
for item in taken:
print(' ', item)
def testGreedys(maxWeight = 20):
items = buildItems()
print('Use greedy by value to fill knapsack of size', maxWeight)
testGreedy(items, maxWeight, value)
print('\nUse greedy by weight to fill knapsack of size',
maxWeight)
testGreedy(items, maxWeight, weightInverse)
print('\nUse greedy by density to fill knapsack of size',
maxWeight)
testGreedy(items, maxWeight, density)

图12-4 使用贪婪算法选择物品

testGreedys()执行完毕后,会输出以下结果:

Use greedy by value to fill knapsack of size 20
Total value of items taken is 200
<computer, 200, 20>
Use greedy by weight to fill knapsack of size 20
Total value of items taken is 170
<book, 10, 1>
<vase, 50, 2>
<radio, 20, 4>
<painting, 90, 9>
Use greedy by density to fill knapsack of size 20
Total value of items taken is 255
<vase, 50, 2>
<clock, 175, 10>
<book, 10, 1>
<radio, 20, 4>

  • 23
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

___Y1

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值