热门标签 | HotTags
当前位置:  开发笔记 > 人工智能 > 正文

编程之美读书笔记卖书折扣问题的贪心解法

《编程之美》读书笔记(四):卖书折扣问题的贪心解法每次看完《编程之美》中的问题,想要亲自演算一下或深入思考的时候,都觉得时间过得很快&#x
《编程之美》读书笔记():卖书折扣问题的贪心解法

每 次看完《编程之美》中的问题,想要亲自演算一下或深入思考的时候,都觉得时间过得很快,动辄一两个小时,如果再把代码敲一遍的话,需要的时间可能更长,真 是搞不懂通过微软面试的那些家伙的脑袋到底什么构造,书的序言中提到他们每次面试45分钟,还要写出程序?!在我看来,如果是控制CPU曲线或是中国象棋 问题或许还有可能,如果是买书折扣问题,我觉得真的是不太容易,尤其是如果当面试者钻进本题的贪心解法而不是动态规划算法的思路之后,因为我写这篇文章前 前后后大概用了5个小时:-( 。不过我想只要是学习就不是浪费时间,今天上网看到微软的校园招聘网站又有更新,等我把这本书看完,就投简历过去试一试 :-) 。

1 问题描述及分析

买书折扣问题的描述是,某出版社的《哈里波特》系列共有5卷,每本单卖都是8块钱,如果读者一次购买不同的k(k>=2)卷,就可以享受不同的折扣优惠,如下所示:

问题是如果给定一个订单,如何计算出最大的折扣数?

书中给出的动态规划解法这里就不再赘述了。不过,里面有两个问题需要单独关注一下:(1)如果订单描述为(X1,X2,X3,X4,X5),其中X1-X5为所订数的数量,其所在位置为卷的编号,即第一卷X1本,第二卷X2本,…,第5卷X5本;如果订单为:(X3,X2,X4,X5,X1),则表示第一卷X3本,第二卷X2本,…,第五卷X1本。我们可以很容易的看到,由于每本书的价格相同,所以折扣的多少仅仅在于如何选取而不在于究竟取那一卷书。因此,上面两个订单的最大折扣数是相同的,这也使得我们可以使用统一的方法(Y1,Y2,Y3,Y4,Y5),其中Y1≥Y1≥Y1≥Y1≥Y5,来表示一个订单。作者将每本书的定价设为相同的是为了简化问题,因为如果每本书的定价也不同,则问题就会变得更加复杂,这时我们就不能仅仅考虑选几本书,还要考虑选哪几本。(2)书中说对于一次选择4至2本书的情况,(以3本书为例)只需考虑F(Y1-1,Y2-1,Y3-1,Y4,Y5)的情况就可以了(注意,F为订单总价计算函数),并说“这样选择能够保证在选择当前折扣的情况下,剩下的书的种类最多,它比其它组合都好”。 我觉得这个结论并不显然,下一步可选的书的种类多就能证明这个子问题比别的子问题更好?这点我不敢苟同,须知该问题的解决是一个多步选择的过程,所以要得 到这个结论就需要严格证明。如果这个条件不成立,那么书中给出的递归式也就不成立,即不能证明优化子结构性质成立。所以,对于如此重要的细节,书中应该给 出严格证明而不是一句话带过。

刚 开始拿到问题的时候就条件反射地想能不能用贪心算法,即每次都尽量按最大折扣来取书。书中给出一个反例:所给的订单是(2,2,2,1,1),按照贪心算 法,我们的选择方式是5+3,其折扣为5*0.25+3*0.10=1.55;而如果采用4+4的模式,则折扣数为2*4*0.20=1.6。这显然违背 了贪心规则。所以书中在解法二中采用了另外一种分析,作者计算了订单中书的数量在[1-10]区间内,各种不同的选取方法所能获得的最大折扣数:

本数
可能的分解本数
对应的折扣
对于2-5本,
直接按折扣
购买
2
3
4
5
5%
10%
20%
25%
6
=4+2
=3+3
=2+2+2
0.9
0.6
0.3
7
=5+2
=4+3
1.35
1.1
8
=5+3
=4+4
=3+3+2
=2+2+2+2
5*25%+3*10%=1.55
4*20%+4*20%=1.6
0.7
0.4
9
=5+4
=5+2+2
=4+3+2
=3+3+3
2.05
1.45
1.2
0.9
10
=5+5
=4+4+2
=4+3+3
=2+2+2+2+2
2.5
1.7
1.4
0.5


表1-2 折扣计算表

看 上面给出的折扣计算表,我们可以简单分析一下。实际上这个反例产生的原因是3本书的折扣数量与4本书的折扣数量差距过大造成的。实际上只要我们将题目稍微 改动一下,将三本书的折扣改成15%,得到表1-2所示的折扣,我们就会发现贪心算法奇迹般地生效了!(为清楚起见,我把修改前和修改后的折扣计算结果放 到了一张表中)那么如果一开始作者给出的就是三本书15%的折扣的话,从表中就可以看出使用原始的贪心算法所得到的结果是对的,就可能会连带得出贪心算法 有效的结论?!我有点后怕!

很显然,如果可以应用贪心算法的话,贪心选择不应该局限于某一种折扣数量的设定。而如果《哈利波特》不是5卷而是M卷,折扣表扩大的话,就很可能会产生更多的反例情况。

本数
可能的分解本数
原始的折扣
新的折扣
对于2-5本,
直接按折扣
购买
2
3
4
5
5%
10%
20%
25%
5%
15%
20%
25%
6
=4+2
=3+3
=2+2+2
0.9
0.6
0.3
0.9
0.9
0.3
7
=5+2
=4+3
1.35
1.1
1.35
1.25
8
=5+3
=4+4
=3+3+2
=2+2+2+2
5*25%+3*10%=1.55
4*20%+4*20%=1.6
0.7
0.4
5*25%+3*15%=1.7
1.6
3*15%*2+2*10%=1.0
0.4
9
=5+4
=5+2+2
=4+3+2
=3+3+3
2.05
1.45
1.2
0.9
2.05
1.45
0.8+0.45+0.1=1.35
0.45*3=1.35
10
=5+5
=4+4+2
=4+3+3
=2+2+2+2+2
2.5
1.7
1.4
0.5
2.5
1.7
1.7
0.5


表1-3与贪心策略相符合的折扣表

于 是作者想把多余10本的订单分成若干个小于10的订单组,并把每组的最大折扣相加,以得到全局最优解,关于这一点我将在下面进行说明。作者在最后讲述了修 改贪心策略的方法,不过我不是太理解,而且其中还有一些错误,最重要的是作者最终也没能证明其正确性,我就不再深究了。

2 贪心算法是否适用的分析

贪 心算法的适用有两个必要条件,即优化子结构和贪心选择性。第一个性质由于已经证明可以适用动态规划算法,所以优化子结构性质显然成立(假如书中的动态规划 递归式成立的话)。现需要证明其贪心选择性,即如何“贪心”的进行选择。显然每次都查找最大的折扣数进行处理的贪心方法是行不通的,那么是贪心方法真的不 行还是我们“贪”的不正确呢?我们下面就来分析。

贪 心选择性的含义是,一个全局最优解可以通过局部最优选择来达到,换句话说,当考虑作何选择时,我们只考虑对当前问题最佳的选择而不考虑子问题的结果。贪心 算法所做的当前选择可能要依赖于已经做出的所有选择,但不依赖于有待于做出的选择或子问题的解。所以,贪心策略通常是自顶向下地,一个一个地做出贪心选 择,不断地将给定的问题归约为更小的问题。当然,在此之前我们必须证明在每一步所做的贪心选择最终能产生一个全局最优解,这也正是本题的关键所在。

书中解法二给出的分组的思想是可以借鉴的,但是显然犯了方向性的错误。因为贪心算法的关键在于“选择”, 即从当前的状态来“贪心地”从多种子状态中选择一个“当前的”最优解进行下去,并由其贪心选择性而使最终的解刚好是最优解。既然书中最后采用的还是(经修 改后的)贪心算法,就不应该把整体的问题分成若干组来执行。这是因为贪心算法的上一次选择与下一次选择之间是带有连续性的,并不能够将它们拆开处理;而如 果要拆开处理之后再将结果相加,就还需要证明拆分是使得结果最优的拆分。所以我们的目标最终还是应该定为寻找如何“贪”!其实作者的意图是对的,但实际目 标应该定为限制本次选择对于此后选择的影响,或使本次选择的影响仅限于下一次选择,这也是表格1-1只需要计算到10的原因(两次选择最多选择10本书),但由于下一次选择的影响可能会影响下下次的选择,所以我们不能硬性将这些选择拆开然后再相加,而只能一次一次地完成选择。

2.1特殊情况

首先,让我们分析一下2-1左图所 示的订单,很显然,蓝框所框住的15本书都应该按照5本书的折扣处理。我们之所以“敢”这么做的原因是这次的处理不会影响到下一次的“选择”。即如果蓝框 的范围再向上扩大一层的话,一次选择之后就会使得最后两列书的数量为0,并导致下次选择时最多只有3本书的折扣可以选择,相当于间接地选择了“5+3”模 式。而如果蓝框的范围如图2-2(左)所示的话,这两次处理我们还可以有“4+4”模式可供选择,而这才是最佳选择。所以,当我们的选择不会使列数减少的 时候,我们可以放心大胆地选择利润最大的那选择法;同样在2-1右图(注意:左右两图并无关联,是两个不同的订单),由于第五列已经没有了,在蓝框范围内的书籍我们仍然应该按照左图的做法处理,只是这次我们的折扣是4本书的。

再细想一下,如果我们不选择折扣最大的(即书的数量最多的)那个选择,按照书中给出的动态规划子问题的定义中的取法:如果取4本书是从前四列取,取3本书从前三列取,依次类推,我们权且称其为“最小取法”(与书中的“最小表示”相呼应),我们可以知道,这一次不选择折扣最大只是将折扣最大的选择“推后”了。因为贪心算法每一步的选择都应该是当前状况下的最优选择,即我们在每一步都应该尽可能多获得折扣,而不应该将折扣“推后”而导致我们本次的选择并非最佳选择。换句话说,我们应该保证下一次选择的书的数量不应该比本次选择的数量多,即我们可以允许两次的选择是5+3或4+4,但绝不能允许3+5出现。作者给出的表1-1也印证了这一点,我们可以看到,表中所有的拆分都是降序排列的,即下一次可选择的最大数量不会超过本次选择的书的数量


例如2-2左图中,如果我们本次选择(最底层)只选择了3本书,那么上一段定义的最小取法,我们得到的子问题应该是F(Y1-1,Y2-1,Y3-1,Y4,Y5), 于是下一次我们仍然可以选择5本书,大于本次所选的三本书,这是不应该发生的。而如果我们选择5本书,下一次只有3本书可以选择,这显然是可以的,但并不 是最优的;如果我们选择4+4呢?显然不违反我们刚才定下的规则,而且折扣最大。下面我们的问题就是如何对原始的贪心定义稍作修改,使其能够帮我们挑出最 大折扣的选法。

在处理掉一些特殊情况之后,我们的订单变为2-2左图所示的情况。而且前面我们定义:本次选择的书的数量不应该小于下一次可选择的书的最大数量(其中,书的取法按照书中动态规划方法给出的选法来进行)。 也就是说,我们不应该把最大的折扣选择“推后”执行,而使得本次选择不是当前看起来“最优的”。其次,这种选取方式也使得本次选择的影响仅限于下一次(即 图中红色框中的范围),而不会“扩散”到下一次选择之后的选择,例如下下次选择。同时,下下次选择也只受下次选择的影响,由此我们成功地阻止了本次选择对 整体带来的影响。

2.2修改的贪心算法

对于2-2左图所示的情况&#xff0c;在满足上述条件的前提下&#xff0c;我们目前有5和4两种选择&#xff08;当然&#xff0c;如果书的卷数为M>5的话&#xff0c;可能的选择也许会更多&#xff09;&#xff0c;我们如何在不引发贪心反例的情况下获得最大折扣呢&#xff1f;我的答案是“查表”。此时&#xff0c;我们应该查阅表1-2&#xff0c;找到其中折扣数最大的那个最为本次的选择。我们可以看到&#xff0c;因为5&#43;3<4&#43;4&#xff0c;所以我们本次应该选择4本书。我们还需要验证&#xff0c;下一次选择我们还需要选择4本书。此时状况如图2-2右图所示&#xff0c;我们的选择只有4&#43;3&#xff0c;因为如果本次我们选择3本书&#xff0c;下一次我们还可以选择4本书&#xff0c;这与我们的规则不符。所以我们的贪心选择是正确的。其余的书籍我们可以按照上面提到的两种情况加以处理即可。


经过修改后&#xff0c;贪心算法的空间复杂度变为计算如1-2所示的表格的代价&#xff0c;而修改后的贪心算法的时间复杂度就很直观&#xff0c;应该为我们做出选择的次数&#xff08;包括一次查表操作&#xff09;&#xff0c;也就是O&#xff08;Y1&#xff09;。

3总结

本问题的修改后的贪心算法的规则是&#xff1a;

1 本次选择的书的数量不应该小于下一次可选择的书的最大数量&#xff08;其中&#xff0c;书的取法按照书中动态规划方法给出的选法来进行&#xff09;

2 每一次选择之前都应该查表&#xff0c;选择其中使得近两次折扣数最大的那个作为本次选择&#xff08;因为我们使得本次选择的影响被控制在两次选择的范围内&#xff09;。

图2-1左图中蓝框内的情况最终也可以归结为后面所分析的状况中的一种。即因为下一次只有5 可以选择&#xff08;少于五本将违反规则1&#xff09;&#xff0c;所以本次就选择5本书。此外&#xff0c;当书的卷数变为M(M>5)的时候&#xff0c;表1-2需要的计算的数量也会相应变大。但实 际上表1-2可以简化&#xff0c;例如我们看8本书的情况&#xff0c; 3&#43;3&#43;2和2&#43;2&#43;2&#43;2其实都不用考虑&#xff0c;因为按照图2-2左图所示&#xff0c;因为规则1的存在使我们不可能选择3本书。即便是换一个例子&#xff0c;如果我们本次选择3 本书、下一次最多也只能选3本书的话&#xff0c;我们就回到了处理图2-2左图蓝框中的情况当中。最后&#xff0c;如果书的卷数为M&#xff0c;则我们的表格的行数只需要为2M即可&#xff0c;因为我们一次选择的影响仅限于本次和下次&#xff0c;所涉及的书的数量不会超过2M个

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

[1] P33&#xff0c;本数为6的4&#43;2的折扣应该是4*20%&#43;2*5%&#61;0.9而不是1.1

[2] P34&#xff0c;最后一段&#xff0c;第2-3行说原始的“贪心策略建议我们买Y5本卷五&#xff0c;Y4-Y5本卷四&#xff0c;Y3-Y4本卷三&#xff0c;Y2-Y3本卷二和Y1-Y2本卷一”&#xff0c;对于订单&#xff08;2&#xff0c;2&#xff0c;2&#xff0c;1&#xff0c;1&#xff09;&#xff0c;其中前三卷每卷两本&#xff0c;后两卷每卷一本&#xff0c;贪心的规则应该是5&#43;3&#xff0c;即第一次选择应该每卷拿一本才对。而文中说第四卷拿Y4-Y5&#61;0本&#xff1f;所以这段文字我认为不妥。而且对于这一段后面的内容我也不是太明白&#xff0c;我个人觉得有修改一下的必要。

[3] 同样是P34最后一段&#xff0c;最后三行。虽然我对这一段的意思不太了解&#xff0c;但是仍然可以看出一个明显的错误&#xff0c;那就是最后三行所举的例子提到“要买3本第一卷&#xff0c;2本第二卷….”&#xff0c;而在最后却说“经调整后的贪心策略告诉我们应该买三本四卷&#xff0c;三本两卷….”&#xff0c;前面说第二卷一共就两本&#xff0c;后面却说要买3本第二卷&#xff0c;真不知道这个贪心策略是怎么“告诉的”。我建议&#xff0c;反正由于书的价格导致订单上的书到底第几卷不重要&#xff0c;就不如干脆不用第几卷&#xff0c;改用第几列更为合适。

推荐阅读
  • 探索电路与系统的起源与发展
    本文回顾了电路与系统的发展历程,从电的早期发现到现代电子器件的应用。文章不仅涵盖了基础理论和关键发明,还探讨了这一学科对计算机、人工智能及物联网等领域的深远影响。 ... [详细]
  • 优化ListView性能
    本文深入探讨了如何通过多种技术手段优化ListView的性能,包括视图复用、ViewHolder模式、分批加载数据、图片优化及内存管理等。这些方法能够显著提升应用的响应速度和用户体验。 ... [详细]
  • Søren Kierkegaard famously stated that life can only be understood in retrospect but must be lived moving forward. This perspective delves into the intricate relationship between our lived experiences and our reflections on them. ... [详细]
  • 本文将介绍如何编写一些有趣的VBScript脚本,这些脚本可以在朋友之间进行无害的恶作剧。通过简单的代码示例,帮助您了解VBScript的基本语法和功能。 ... [详细]
  • 1:有如下一段程序:packagea.b.c;publicclassTest{privatestaticinti0;publicintgetNext(){return ... [详细]
  • C++实现经典排序算法
    本文详细介绍了七种经典的排序算法及其性能分析。每种算法的平均、最坏和最好情况的时间复杂度、辅助空间需求以及稳定性都被列出,帮助读者全面了解这些排序方法的特点。 ... [详细]
  • 本文详细探讨了Java中的24种设计模式及其应用,并介绍了七大面向对象设计原则。通过创建型、结构型和行为型模式的分类,帮助开发者更好地理解和应用这些模式,提升代码质量和可维护性。 ... [详细]
  • 深入理解C++中的KMP算法:高效字符串匹配的利器
    本文详细介绍C++中实现KMP算法的方法,探讨其在字符串匹配问题上的优势。通过对比暴力匹配(BF)算法,展示KMP算法如何利用前缀表优化匹配过程,显著提升效率。 ... [详细]
  • 机器学习中的相似度度量与模型优化
    本文探讨了机器学习中常见的相似度度量方法,包括余弦相似度、欧氏距离和马氏距离,并详细介绍了如何通过选择合适的模型复杂度和正则化来提高模型的泛化能力。此外,文章还涵盖了模型评估的各种方法和指标,以及不同分类器的工作原理和应用场景。 ... [详细]
  • 自学编程与计算机专业背景者的差异分析
    本文探讨了自学编程者和计算机专业毕业生在技能、知识结构及职业发展上的不同之处,结合实际案例分析两者的优势与劣势。 ... [详细]
  • 2023年京东Android面试真题解析与经验分享
    本文由一位拥有6年Android开发经验的工程师撰写,详细解析了京东面试中常见的技术问题。涵盖引用传递、Handler机制、ListView优化、多线程控制及ANR处理等核心知识点。 ... [详细]
  • 本文详细介绍了如何在Ubuntu系统中下载适用于Intel处理器的64位版本,涵盖了不同Linux发行版对64位架构的不同命名方式,并提供了具体的下载链接和步骤。 ... [详细]
  • MySQL索引详解与优化
    本文深入探讨了MySQL中的索引机制,包括索引的基本概念、优势与劣势、分类及其实现原理,并详细介绍了索引的使用场景和优化技巧。通过具体示例,帮助读者更好地理解和应用索引以提升数据库性能。 ... [详细]
  • 本文将详细介绍如何在Linux操作系统中执行PHP脚本,包括环境配置、命令使用及验证方法。对于需要在Linux环境下开发或部署PHP应用的用户来说,这是一篇非常实用的文章。 ... [详细]
  • 线性Kalman滤波器在多自由度车辆悬架主动控制中的应用研究
    本文探讨了线性Kalman滤波器(LKF)在不同自由度(2、4、7)的车辆悬架系统中进行主动控制的应用。通过详细的仿真分析,展示了LKF在提升悬架性能方面的潜力,并总结了调参过程中的关键要点。 ... [详细]
author-avatar
shurui26jx_882
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有