学习安排(8月16日-8月20日)
1.主要学习视频Week6
链接(http://www.xuetangx.com/courses/MITx/6_00_2x/2014_T2/courseware/d39541ec36564a88af34d319a2f16bd7/)
2.参考书第十二章--背包与图的最优化问题
解决问题时,如果涉及求最大、最小、最多、最少、最快、最低价格等情况,那么你就非常有可能将这个问题转换为一个典型的最优化问题,从而使用已知的计算方法进行解决。最优化问题通常包括两部分:
- 目标函数:需要最大化或最小化的值。例如,波士顿和伊斯坦布尔之间的飞机票价。
- 约束条件集合(可以为空):必须满足的条件集合。例如旅行时间的上界。
背包问题
假设窃贼有一个背包,最多能装20磅赃物,他闯入一户人家,发现图1中的物品。很显然,他不能把所有物品都装进背包,所以必须确定拿走哪些物品,留下哪些物品。他需要找出一组能够带走的价值最高的东西。
贪婪算法
对于这个问题,找出近似解的最简单方法就是贪婪算法。窃贼会首先选择最好的物品,然后是次好的,这样继续下去,直到将背包装满。当然,在此之前,窃贼必须确定什么是“最好”的。最好的物品是价值最高的,重量最轻的?还是具有最高价值/重量比值的呢?如果选择价值最高的物品,就应该只带电脑离开,这样可以得到200美元。如果选择重量最轻的,那么应该依次带走书、花瓶、收音机和油画,一共价值170美元。最后,如果确定“最好”的含义是价值/重量比值最高,那么应当首先拿走花瓶和钟。然后有三种物品的价值/重量比值都是10,但背包里只能放下书了。拿走书之后,他还可以拿走收音机。这样,所有赃物的价值是255美元。
对于这个数据集,尽管按照密度(价值与重量的比值)进行贪婪恰好得到了最优结果,但相对于按照重量或价值进行贪婪的算法来说,我们不能保证按照密度贪婪的算法一直能得到更好的解。更普遍地说,对于这种背包问题,无法确保使用贪婪算法找出的解是最优解。
0/1背包问题的最优解
假设我们认为近似解还不够好,那就需要找出这个问题的最优解。窃贼面临的问题恰好就是一种典型的最优化问题,称为0/1背包问题。 0/1背包问题可以定义如下。
- 每个物品都可以用一个值对<价值, 重量>表示;
- 背包能够容纳的物品总重量不能超过w;
- 长度为n的向量I表示一个可用的物品集合,向量中的每个元素都代表一个物品;
- 长度为n的向量V表示物品是否被窃贼带走。如果V[i] = 1,则物品I[i]被带走;如果V[i] = 0,
则物品I[i]没有被带走; - 目标是找到一个V,使得:
的值最大,并满足约束条件:
我们看看如何简单直接地解决这个问题。
(1) 枚举所有可能的物品组合。也就是说,生成物品集合的所有子集,即物品集合的幂集。
(2) 去掉所有超过背包允许重量的物品组合。
(3) 在余下的物品组合中,选出任意一个价值最大的组合。
这种方法一定可以找到一个最优解。但如果初始物品集合很大,就需要运行很长时间。原因是随着物品数量的增长,子集数量呈现指数型增长。
图的最优化问题
一些典型图论问题
有很多著名的算法可以用来解决图的最优化问题,这是使用图论表示和解决问题的一个优势。以下是一些最著名的图的最优化问题。
- 最短路径:对于两个节点n1和n2,找到边<s___n___, d___n___>(源节点和目标节点)的最短序列,使得:
(1)第一条边的源节点是n1;
(2)最后一条边的目标节点是n2;
(3)对于序列中任意的边e1和e2,如果e2在序列中紧跟在e1后面,那么e2的源节点是e1的目标节点。 - 最短加权路径:与最短路径非常相似,但它的目标不是找出连接两个节点的最短的边的序列。对于序列中边的权重,我们会定义某种函数(比如权重的和),并使这个函数的值最小化。 Google Maps计算两点之间的最短驾驶距离时,就是在解决这种问题。
- 最大团: 团是一个节点集合,集合中每两个节点之间都有一条边。 最大团是一个图中规模最大的团。
- 最小割:在一个图中,给定两个节点集合, 割就是一个边的集合。去掉这组边之后,一个节点集合中的每个节点和另一个节点集合中的每个节点之间都不存在任何相连的路径。最小割就是这样一个最小的边的集合。
最短路径:深度优先搜索和广度优先搜索
Facebook上具有“朋友”关系的两个人之间的距离。例如,你
会很想知道,你是否有这样一个朋友,他的朋友的朋友的朋友是米克·贾格尔。我们可以设计一个程序来回答这个问题。
朋友关系(至少在Facebook上)是对称的,例如,如果斯蒂芬妮是安德烈娅的朋友,那么安德烈娅也是斯蒂芬妮的朋友。因此,我们会使用Graph类型实现这个社交网络。然后可以定义寻找你和米克·贾格尔之间的最短连接的问题,如下所示。
- 令G为表示朋友关系的无向图;
- 对于G,找到一个最短的节点序列[你,……,米克·贾格尔],使得:如果和是路径中两个连续的节点,那么G中有一条边连接和。
from graph import *
def printPath(path):
"""假设path是节点列表"""
result = ''
for i in range(len(path)):
result = result + str(path[i])
if i != len(path) - 1:
result = result + '->'
return result
def DFS(graph, start, end, path = [], shortest = None):
#assumes graph is a Digraph
#assumes start and end are nodes in graph
path = path + [start]
print 'Current dfs path:', printPath(path)
if start == end:
return path
for node in graph.childrenOf(start):
if node not in path: #avoid cycles
newPath = DFS(graph,node,end,path,shortest)
if newPath != None:
return newPath
def DFSShortest(graph, start, end, path = [], shortest = None):
#assumes graph is a Digraph
#assumes start and end are nodes in graph
path = path + [start]
print 'Current dfs path:', printPath(path)
if start == end:
return path
for node in graph.childrenOf(start):
if node not in path: #avoid cycles
if shortest == None or len(path)<len(shortest):
newPath = DFSShortest(graph,node,end,path,shortest)
if newPath != None:
shortest = newPath
return shortest
def testSP():
nodes = []
for name in range(6):
nodes.append(Node(str(name)))
g = Digraph()
for n in nodes:
g.addNode(n)
g.addEdge(Edge(nodes[0],nodes[1]))
g.addEdge(Edge(nodes[1],nodes[2]))
g.addEdge(Edge(nodes[2],nodes[3]))
g.addEdge(Edge(nodes[2],nodes[4]))
g.addEdge(Edge(nodes[3],nodes[4]))
g.addEdge(Edge(nodes[3],nodes[5]))
g.addEdge(Edge(nodes[0],nodes[2]))
g.addEdge(Edge(nodes[1],nodes[0]))
g.addEdge(Edge(nodes[3],nodes[1]))
g.addEdge(Edge(nodes[4],nodes[0]))
sp = DFS(g, nodes[0], nodes[5])
print 'Shortest path found by DFS:', printPath(sp)
def BFS(graph, start, end, q = []):
initPath = [start]
q.append(initPath)
while len(q) != 0:
tmpPath = q.pop(0)
lastNode = tmpPath[len(tmpPath) - 1]
print 'Current dequeued path:', printPath(tmpPath)
if lastNode == end:
return tmpPath
for linkNode in graph.childrenOf(lastNode):
if linkNode not in tmpPath:
newPath = tmpPath + [linkNode]
q.append(newPath)
return None
DFS函数实现的算法是递归形式的深度优先搜索算法。一般地,深度优先搜索算法开始时,会先选择起始节点的一个子节点,然后再选择这个子节点的一个子节点,以此类推,直到到达目标节点或者一个没有子节点的节点。然后,搜索开始回溯,返回到最近一个没有访问过的带有子节点的节点。遍历所有路径之后,算法就可以选择一个从起点到终点的最短路径(如果有)。
除了深度优先之外,还有其他方式对图进行遍历。另一种常用的方法称为广度优先搜索。广度优先搜索会先访问起始节点的所有子节点,如果这些子节点都不是最终节点,就继续访问每个子节点的所有子节点,以此类推。深度优先搜索经常使用递归实现,广度优先搜索则不同,它一般使用迭代来实现。 BFS会同时探索多条路径,每次迭代向每条路径添加一个节点。由于算法生成路径时是按照长度升序进行的,所以第一次找到的最终节点为目标节点的路径一定具有最少数量的边
在本例中,两种算法找出的是同样的路径。但如果图中两个节点之间的最短路径不止一条,那么DFS和BFS就不一定会找出同样的
最短路径。如上所述,如果要找出一条边数最少的路径,那么BFS更方便,因为它第一次找到的路径就一定是这样的路径。