1.6节:饮料供货

来源:互联网 发布:vc数字图像处理编程 编辑:程序博客网 时间:2024/04/28 22:12

作者:薛笛

从买书那天算起,到今天已经过了半个多月。这段时间说短不短,如果是一本300多页的小说的话,我大概一天就能搞定(我的记录是一天一千多页《大唐双龙传》),但是到现在《编程之美》我只看了不到50页。虽然我不是天天看,但是一旦我看了一个问题之后,我就希望能够把这个问题在算法层面分析透,这份专注是我以前看《算法导论》或者上算法课的时候所不曾体会到的。究其原因,主要还是纯粹的理论流于枯燥,纯粹的应用不免肤浅,而这本书的定位刚刚好,既能够以应用带动算法的学习,又能够避免过于说教的风格。

尽管初衷不错,但我仍觉拿捏得尺度有待商榷。因为编程应该既严谨又灵活,严谨的思考保证了程序运行的稳定,而灵活的思维则为创新提供了条件。而“编程之美”给我的感觉是灵活有余严谨不足。我觉得既然是有关算法的书,那么在一些关键点上的证明就不可或缺,例如在我看的六个问题中就出现两个贪心算法没证明其贪心选择性,或讨论的不够,其中“买书问题”的贪心选择还是有问题的。当然,我们不应该奢求这本定位于“面试题集”的书能够写出如“算法导论”般严格的证明。但至少应该给出具体思路,以证明想法是有依据而非直观感觉(因为我们的直观感觉在很多时候是不准确的,就比如我的读书笔记四种分析买书问题,如果把三本书的折扣改成15%,那我们就有可能被得到的折扣表格所误导而采用原始的贪心算法);最可怕的是鉴于构建反例实际是一件非常困难的事情,所以我们从给出的例子中可能也无法看出端倪。此外,在看书过程中所遇到的各种错误的数量也是屡创新高。听说马上就要出第二版了,真是为后面的内容担心,难道最后又要附一页勘误表?

虽然问题多多,但由于瑕不掩瑜,我们仍然能够从书中获得足够多的微软人的智慧,哪怕是促使我们重新温习一遍算法基础也好,希望第四版( 如果有的话:-) )能看到一个完美的“编程之美”。

    1 问题描述及分析

本题所说的问题是微软每天为员工提供各种不同的饮料,如可乐,酸奶,豆浆,咖啡,绿茶……..(待遇不错啊,呵呵),饮料i的单位容量为Vi,其中每种饮料单个容量都是2的方幂,员工对饮料i的满意度为Hi,冰柜的总容量为V(每天必须装满),问题是如何组合现有的各种饮料,使总的满意度最高。

还是说一下我的第一印象,很显然这是一道最优化问题,但很容易想到这道题和线性规划的描述很符合,但是由于解线性规划的单纯型方法比较复杂,这里就不再讨论了。其次,回想一下经典的0-1背包问题,和本问题也有些相似,所不同的是0-1背包问题中,每件物品只能拿一次,而这里同一种饮料能拿多瓶;此外,原问题中每天供应的总量V是必须达到的(否则会有员工投诉?),所以不能够像0-1背包问题那样有让背包装不满的情况。这个条件实际上改变了我们对于最优解的搜索策略,因为容量为V的装饮料的冰柜每天早晨都必须是满的,所以即便有另一个使满意度最高但冰柜不满的组合我们也是不能选的。

其实我们可以稍微改变一下本题的条件,忽略原问题中的每种饮料单个容量都是2的方幂的条件,并允许冰柜不满的情况下求最大满意度的组合,希望可以使原问题的解决更富有一般性。

    2 算法分析2.1 动态规划算法

没有悬念,优化问题就用动态规划、贪心算法、分支限界轮番上阵就好了。设OptV’i)表示从in-1种饮料中,Ci为第i种饮料可能的最大数量,算出总量为V’的方案中满意度之和的最大值。那么递归式就应该是:

OptV’i=max{ k * Hi+OptV’-Vi * ki+1}k=012…Cii=012…n-1

这里我觉得需要说明给出的饮料组合最终可以组合出V

2.2 贪心算法

书中的贪心解法似曾相识,把信息按照饮料的容量排序(其中设我们有n0种容量为20的饮料):

Volume

TotalCount

Happiness

20

TC0_0

H0_0

20

TC0_1

H0_n0

21

TC1_0

H0_0

 

2M

TCM_0

HM_0

 

 

然后按照下面的顺序进行贪心选择:

(1)       饮料总量为1,从容量为20的饮料中选出快乐指数最大的。

(2)       饮料总量为2,从容量为21的饮料中选出快乐指数最大的(设为H1),与容量为20的饮料中快乐指数最大的(设为H0),比较H12* H0,取出其中最大者为当前最佳选择

(3)       继续进行下去,直到求出OptV0

粗看一下,有些似曾相识。我们在买书问题的时候也曾面临将买书计划拆分,然后查表进行贪心选择。然而买书问题每次选择至多选M本书,而且每次选择只影响下一次选择,所以只需要把2M进行有限的拆分即可。

而本题则不尽相同,对于某种容量V’(以V’=11为例)来说,有两个问题:

1.    首先,我们需要察看所有拆分的可能性,找出其中最大者作为本次贪心选择的结果。其中,由于“每种饮料的单位容量都是2的方幂”,所以拆分结果仅考虑用小于V’2的方幂来进行组合,即(计算式111=8+2+14+4+2=14+2+2+2+1….1+1+…+1。可以看到,对于V’我们至少可以“拆”出V’种组合(或许更多),即便我们把每次的计算结果用表格保存起来,我们的查找次数也至少是Ω1+2+…+V’=V’2),空间复杂度也很高,并没有如数中说“简单很多”。而且,如果取消“每种饮料的单位容量都是2的方幂”的限制,拆分的结果就会变成(计算式211=10+19+25+55+5+15+4+2…..,导致查找次数进一步增加。

2.    其次,让我们回到贪心选择的定义上来。由于贪心选择性,贪心算法的过程是每次选择都选取当前看来最好的结果,使得当达到最终状态时的结果刚好是最优解。而我们再看V’=11的例子,假设最优解是4+4+2+1,则我们曾经计算过的8就不是最优解的一部分,这就和贪心算法的精神不符,所以这个方法其实还是动态规划。

    3 总结

动态规划解法很好,贪心算法有待商榷。其实这是正常的,因为通常情况下使用贪心算法的难点在于证明贪心选择性的存在,鉴于这本书的定位,我们不能苛责更多。但是这个问题和上一个问题(买书折扣问题)的贪心算法我觉得都过于草率。如果想让这本书成为经典,“微软面试”的噱头是远远不够的,精益求精的态度至关重要,有时候真的没必要把Deadline设置的那么紧迫,偶尔跳票追求质量也是理所应当,看看人家暴雪的“星际争霸2”跳票这么多年就知道了…….

    4 《编程之美》本问题勘误

[1] P41,第七段,“OptV’i)表示从第ii+1n-1n种饮料”,应该把n去掉,因为i的取值范围是[0n-1]

[2] P41,第八段,“OptVn)”应该改为OptV0)。

[3] P41,推导公式,应改为max{ … +Opt(V’-Vi * k, i+1)}i=n-2n-1……0

[4] P42,代码清单1-9for (int i=0; i <= V; i++),应改为for (int i=1; i <= V; i++),因为Opt[v][n]的第一行都是0,就不用计算了。

[5] P42,代码清单1-9,“if ( i<=k*V[j] )”应该改为if ( i < k*V[j] ),因为显然等号情况应该保留

[6] P42,最后一段,应改为计算Opt[i][j]时只需要Opt[k][j+1](0<=k<=V)这一列,所以不必列出整个矩阵,而只需要两列即可。

[7] P44,解法三种的表的第三行,TC0_1,应该改为TC0_n0

(原文地址:http://blog.csdn.net/kabini/archive/2008/04/21/2311946.aspx

原创粉丝点击