第一周 背景
第一周主要介绍了一下课程背景,关于离散优化的一些概念。
假设一个即将倒塌的博物馆里有很多藏品,你有一个小包,可以选择一些物品背走,使得保存的价值最大。背包的最大capacity是K,每个物品有自己的value,和weight。也就是在选择物品的weight和小于等于K的条件下,使value的和最大。即knapsack问题。
这也就是最优化问题,是计算科学中最难的NP难问题之一。
NP难问题有两个属性:1.可以快速检查某个方法是不是NP问题的解
2.如果一个方法可以很快解决一个NP难问题,那么它肯定可以解决所有NP难问题。
两个思路解决NP难问题:1.将曲线右移2.处理前端近似线性关系的曲线。
最优化问题举例:肾移植匹配问题;灾难管理问题;电量储备问题
第二周 knapsack问题
greedy算法
根据第一周介绍的背景,如何使背包带走的藏品价值最大。
第一个很简单的思想就是价值贪婪思想。先挑最值钱的,再根据袋子里还剩多少空间,选择重量小于剩余capacity的价值最大的,以此类推。
对于一个问题,可以有多种贪婪算法。
第二种贪婪算法是背包能装的东西越多越好。
第三种最科学,优先选择单位重量价值最高的藏品。
然而以上都是不是最佳的选择,很明显,选择两块价值为10的碑文,符合背包容量的要求,可以带来最大的收益20。
贪婪算法的优点:1.很容易实现2.运算快
问题:1.无法保证最优2.只能解决简单问题3.根据输入的不同效果不同
going beyond greedy:1.constraint programming2.local search3.mixed ineger programming
knapsack问题建模
得到的可能结果为2^I,当I的数目越大时,计算复杂度呈指数增长。
DP算法
动态算法是应用广泛的优化技术,具有自下而上的计算思想。
算法思想
O(k,j)表示为背包容量为k,待选择物品数量为j时的获得价值的最优结果。
假设我们已知O(k,j-1),期望得到O(k,j),那么我们只需要考虑第j个物品。
If wj ≤ k:
•如果不选择j物品,最优解保持不变,仍为O(k,j-1)
•如果选择j物品,最优解变为vj + O(k-wj,j-1)
总而言之:
•O(k,j) = max(O(k,j-1), vj + O(k-wj,j-1)) if wj ≤ k
•O(k,j) = O(k,j-1) otherwise
其中O(k,0)=0
因此DP算法是一个不断迭代的算法,最终的优化结果取决于上一个最优结果,以此递推。
课中以 finding fibonacci numbers程序为例,说明了这种递归算法的效率:由于重复计算子问题,导致算法效率低下。
自下而上计算递归方程
通过逐一考虑待选择的物品,列表计算得到当前最优结果。(表中空处均为0)
从总体最优解回溯子问题的最优解,可以得到x的取值。
这种列表方法计算复杂率与k和n的乘积有关,但是对于计算机来说实际上复杂率与输入大小是成指数关系的(输入大小用logk表示)因此只有在k较小时,算法效率较高。
Branch and Bound
对于下图所示最优化问题:
应用穷举法可以得到树状图:
branching指把问题分解为一系列子问题,如树状图的多个枝杈。Bounding指用最乐观的估计结果作为子问题的最优解。
(课中老师把这个最乐观的估计称为relaxation,”最优化就是relaxation的艺术“)
把上述问题中的容量约束作为relaxation对象,得到下图
不考虑容量的约束,所获收益最乐观的估计为128(45+48+35),随着branch的进行,如果确定不选择某项物品,这个最乐观的估计值就会改变。在branch的过程中需要考虑容量约束,当所剩容量为负值时,往后的branch不再继续。其中,branch的顺序是有讲究的:比较当前所有节点的最乐观估计,选择其中最大的节点进行深入branch,然后再比较再选择,这种方法称之为depth-first。
那么还能relaxation其他对象吗?
课中以巧克力举例,思考能否将x在0到1之间任意取值,也就是带走某个物品的一部分。
那么当前问题下的估计值变为92,使用depth-first branch and bound得到如下结果:
branch and bound中的search策略:
①depth-first:舍弃比当前最优值差的节点
②best-first:选择具有最佳估计的节点(图中星标错误)
③least-discrepancy:trust a greedy heuristic
用递增的错误数目(往右)来explore search space(LDS)
第二周作业
第二周作业是knapsack问题。
迭代dp算法代码实现如下
def opt(k,j,v,w):
if j ==0:
return 0
elif int(w[j-1])<=k:
old = opt(k,j-1,v,w)
new = int(v[j-1])+opt(k-int(w[j-1]),j-1,v,w)
now=max(old,new)
return now
else:
return opt(k,j-1,v,w)
def way(s,k,j,v,w,taken):
if j==0:
return taken
if s != opt(k,j-1,v,w):
taken[j-1]=1
s = opt(k-int(w[j-1]), j - 1, v, w)
return way(s,k-int(w[j-1]),j-1,v,w,taken)
s = opt(k,j-1,v,w)
return way(s,k,j-1,v,w,taken)
迭代算法在n大于30之后计算效率非常低。因此我又尝试了列表dp算法:
def table(k,v,w):
c = numpy.zeros((k+1,len(v)+1),dtype=int)
for i in range(len(v)+1):
for m in range(0,k+1,1):
if i == 0 or m==0:
c[m][i]=0
elif int(w[i-1])>m:
c[m][i] = c[m][i-1]
else:
c[m][i]=max(int(v[i-1])+c[m-int(w[i-1])][i-1],c[m][i-1])
return c
def table_way(r,c,table,j,w,taken):
if j ==0:
return taken
elif table[r-1][c-1]!=table[r-1][c-2]:
taken[j-1]=1
return table_way(r-int(w[j-1]),c-1,table,j-1,w,taken)
else:
taken[j-1]=0
return table_way(r,c-1,table,j-1,w,taken)
当k*n大于1500w时,计算效率也有明显下降。
根据以上问题,最后提交的作业是dp列表算法和greedy算法的组合,最终得到了51分
课程指路:https://www.coursera.org/learn/discrete-optimization/home/welcome