
第15章贪心法与动态规划法 随着程序设计中处理的数据量越来越大,对数据处理的高效率需求也越来越迫切,在 这一背景下,算法在程序设计中的作用显得尤其重要。贪心策略、动态规划等一系列运筹 学模型纷纷运用到程序设计中,产生了解决各种现实问题的有效算法。 贪心法(又称贪婪算法)是指:在对问题求解时,总是做出在当前看来是最好的选择 也就是说,不从整体最优上加以考虑,所做出的仅是在某种意义上的局部最优解。贪心算 法不是对所有问题都能得到整体最优解,但对范围相当广泛的许多问题它能产生整体最优 解或者是整体最优解的近似解。 将一个问题分解成为子问题求解,并且将中间子问题的结果保存起来以避免重复计算 的方法,就叫做“动态规划”。动态规划通常用来求最优解,能用动态规划求解的问题,必 须满足最优解的每个局部解也都是最优的。 15.1贪心法 贪心法是一种程序设计中常用的算法策略,在解决一些最优性问题时尤其具有简捷高 效的特点。 15.1.1贪心法的思想 在实际牛活中,经常会调到求一个问题的可行解和最优解的要求。这就是所谓的最优 化问题。每个最优化问题都包含一组限制条件和一个优化函数,符合限制条件的问题求解 方案称为可行解,使优化函数取得最佳值的可行解称为最优解。 贪心法是求解最优化问题的一种常用算法,它是从问题的某个初始解出发,采用逐步 构造最优解的方法向给定的目标推进。在每个局部阶段,都做出一个看上去最优的决策(即 某种意义下的、或某个标准下的局部最优解),并期望通过每次所做的局部最优选择产生出 个全局最优解。做出贪心决策的依据称为贪心准则(策略),但要注意:决策一旦做出 就不可再更改。 贪心法是一种能够得到某种度量意义下的最优解的分级处理方法,通过一系列的选择 来得到一个问题的解,而它所做的每一次选择都是当前状态下某种意义的最好选择,即贪 心选择。每次贪心选择都能将问题化简为一个更小的与原问题具有相同形式的子问题,以 数据处理中常见的选择排序为例,选择排序可以看做逐步确定目标有序序列每一个位置所 放元素的过程,每一步确定当前位置元素的过程就是一个求解局部区间最小值的子问题。 该问题的贪心选择过程可以简单描述为:第1步先获取有序序列第1个位置的元素, 即找出n个元素的最小值:第2步再获取有序序列第2个位置的元素,即找出剩余-l个 元素的最小值::第n-1步获取有序序列第n-1个位置的元素,即找出剩余2个元素 的最小值:剩余1个数即为第n个位置的元素,即剩余1个元素的最小值。这样就把n个
第 15 章 贪心法与动态规划法 随着程序设计中处理的数据量越来越大,对数据处理的高效率需求也越来越迫切,在 这一背景下,算法在程序设计中的作用显得尤其重要。贪心策略、动态规划等一系列运筹 学模型纷纷运用到程序设计中,产生了解决各种现实问题的有效算法。 贪心法(又称贪婪算法)是指:在对问题求解时,总是做出在当前看来是最好的选择。 也就是说,不从整体最优上加以考虑,所做出的仅是在某种意义上的局部最优解。贪心算 法不是对所有问题都能得到整体最优解,但对范围相当广泛的许多问题它能产生整体最优 解或者是整体最优解的近似解。 将一个问题分解成为子问题求解,并且将中间子问题的结果保存起来以避免重复计算 的方法,就叫做“动态规划”。动态规划通常用来求最优解,能用动态规划求解的问题,必 须满足最优解的每个局部解也都是最优的。 15.1 贪心法 贪心法是一种程序设计中常用的算法策略,在解决一些最优性问题时尤其具有简捷高 效的特点。 15.1.1 贪心法的思想 在实际生活中,经常会遇到求一个问题的可行解和最优解的要求,这就是所谓的最优 化问题。每个最优化问题都包含一组限制条件和一个优化函数,符合限制条件的问题求解 方案称为可行解,使优化函数取得最佳值的可行解称为最优解。 贪心法是求解最优化问题的一种常用算法,它是从问题的某个初始解出发,采用逐步 构造最优解的方法向给定的目标推进。在每个局部阶段,都做出一个看上去最优的决策(即 某种意义下的、或某个标准下的局部最优解),并期望通过每次所做的局部最优选择产生出 一个全局最优解。做出贪心决策的依据称为贪心准则(策略),但要注意:决策一旦做出, 就不可再更改。 贪心法是一种能够得到某种度量意义下的最优解的分级处理方法,通过一系列的选择 来得到一个问题的解,而它所做的每一次选择都是当前状态下某种意义的最好选择,即贪 心选择。每次贪心选择都能将问题化简为一个更小的与原问题具有相同形式的子问题,以 数据处理中常见的选择排序为例,选择排序可以看做逐步确定目标有序序列每一个位置所 放元素的过程,每一步确定当前位置元素的过程就是一个求解局部区间最小值的子问题。 该问题的贪心选择过程可以简单描述为:第 1 步先获取有序序列第 1 个位置的元素, 即找出 n 个元素的最小值;第 2 步再获取有序序列第 2 个位置的元素,即找出剩余 n-1 个 元素的最小值;.;第 n-1 步获取有序序列第 n-1 个位置的元素,即找出剩余 2 个元素 的最小值;剩余 1 个数即为第 n 个位置的元素,即剩余 1 个元素的最小值。这样就把 n 个

数的排序问题(全局最优解)分解成次找当前序列中最小值(局部最优解)的子问题, 最后再把个局部最优解组合得到全局最优解。由此看来,贪心算法就是一种在每一步选 择中都采取在当前状态下最好或最优的选择,从而希望得到结果是最好或最优的算法。 从上述简单排序的贪心求解过程,可以初步感受到一般贪心问题求解的基本思路框 架: (1)建立数学模型来描述问题。 (2)把求解的问题分成若干个子问题 (3)对每一子问题求解,得到子问题的局部最优解 (4)把子问颗的局部最优解合成原来问颗的一个解。 尽管通过局部最优解来获取全局最优解的贪心方法并非对所有问题都适合,但在求解 具有最优子结构和贪心选择性质的问题(比如:最短路径问题、最小生成树问题、哈夫曼 编码问题等)时却可以获得整体最优解。这类适合用贪心算法求解的问题常常具备一些相 同或相近的基本特征,比如:问题的数据结构(如:堆、最小树等)具备贪心的特性:或 者问题解决策略具有可证明的贪心特性:或者问题涉及博弈、游戏策略,这些策略大多是 贪心方法:或者问题需要求较优解或多次逼近最优解等。用贪心算法求解诸如此类的问题, 通常可以找到解决问题的最优算法,能够更加高效的完成问题求解任务。而且在这类可用 贪心法求解的问题中,用贪心算法一般比其它算法更加简单、直观和高效 15.1.2贪心法的实现过程 贪心法就是用局部解构造全局解,即从问题的某一个初始解逐步通近给定的目标,以 尽可能快地求得更好的解。当某个算法中的某一步不能再继续前进时,算法停止。 当确认问题可以用贪心法求解之后,贪心实现的基本过程可以分为以下三步: (1)从问题的某个初始解出发。 (2)Whi1e(能朝给定目标前进一步) 求出可行解的一个解元素 (3)由所有部分解组合成问题的 一个可行解 日常生活中大家身边也存在不少贪心的事例,这些事例通常都是采用上述贪心求解步 骤完成求解的。比如我们在超市购物时,经常需要从收银台找零钱,怎么提高收银台找零 钱的效率呢?这是一个典型的贪心问题。根据生活常识,收银员在为顾客找零钱时,为了 提高工作效率、减少顾客的等待时间,总是期望找给每个客户零钱所用的货币张数最少。 为此收银员不是一下给出所有零钱,而是采取逐步找零的贪心方法,即:收银员先给顾客 不超过待找零钱数额的面值最大的第一张零币,然后再给顾客不超过待找零钱余额的面值 最大的第二张零币,·,一直到所给零币总额满足项客待找零钱总额为止。 这样看来,解决找零钱问题的出发点 就是把该问题分成多个子问题,每个子问题的 目标是要找一张不超过当前要找钱数的最大面额的零币,其过程就是一个选择当前子问题 局部最优解的过程。当每个子问题的局部最优解都已经求得,综合以后就可以得到找零钱 张数最少的原问愿的可行解 比如收银员要找的钱为86元,现在的币值体系下,可以找的钱币面额分别为:50 元、20元、10元、5元、1元。首先找一个不超过86元的最大面额的货币,即50元,还 2
2 数的排序问题(全局最优解)分解成 n 次找当前序列中最小值(局部最优解)的子问题, 最后再把 n 个局部最优解组合得到全局最优解。由此看来,贪心算法就是一种在每一步选 择中都采取在当前状态下最好或最优的选择,从而希望得到结果是最好或最优的算法。 从上述简单排序的贪心求解过程,可以初步感受到一般贪心问题求解的基本思路框 架: (1)建立数学模型来描述问题。 (2)把求解的问题分成若干个子问题。 (3)对每一子问题求解,得到子问题的局部最优解。 (4)把子问题的局部最优解合成原来问题的一个解。 尽管通过局部最优解来获取全局最优解的贪心方法并非对所有问题都适合,但在求解 具有最优子结构和贪心选择性质的问题(比如:最短路径问题、最小生成树问题、哈夫曼 编码问题等)时却可以获得整体最优解。这类适合用贪心算法求解的问题常常具备一些相 同或相近的基本特征,比如:问题的数据结构(如:堆、最小树等)具备贪心的特性;或 者问题解决策略具有可证明的贪心特性;或者问题涉及博弈、游戏策略,这些策略大多是 贪心方法;或者问题需要求较优解或多次逼近最优解等。用贪心算法求解诸如此类的问题, 通常可以找到解决问题的最优算法,能够更加高效的完成问题求解任务。而且在这类可用 贪心法求解的问题中,用贪心算法一般比其它算法更加简单、直观和高效。 15.1.2 贪心法的实现过程 贪心法就是用局部解构造全局解,即从问题的某一个初始解逐步逼近给定的目标,以 尽可能快地求得更好的解。当某个算法中的某一步不能再继续前进时,算法停止。 当确认问题可以用贪心法求解之后,贪心实现的基本过程可以分为以下三步: (1)从问题的某个初始解出发。 (2)While(能朝给定目标前进一步) 求出可行解的一个解元素; (3)由所有部分解组合成问题的一个可行解。 日常生活中大家身边也存在不少贪心的事例,这些事例通常都是采用上述贪心求解步 骤完成求解的。比如我们在超市购物时,经常需要从收银台找零钱,怎么提高收银台找零 钱的效率呢?这是一个典型的贪心问题。根据生活常识,收银员在为顾客找零钱时,为了 提高工作效率、减少顾客的等待时间,总是期望找给每个客户零钱所用的货币张数最少。 为此收银员不是一下给出所有零钱,而是采取逐步找零的贪心方法,即:收银员先给顾客 不超过待找零钱数额的面值最大的第一张零币,然后再给顾客不超过待找零钱余额的面值 最大的第二张零币,.,一直到所给零币总额满足顾客待找零钱总额为止。 这样看来,解决找零钱问题的出发点,就是把该问题分成多个子问题,每个子问题的 目标是要找一张不超过当前要找钱数的最大面额的零币,其过程就是一个选择当前子问题 局部最优解的过程。当每个子问题的局部最优解都已经求得,综合以后就可以得到找零钱 张数最少的原问题的可行解。 比如收银员要找的零钱为 86 元,现在的币值体系下,可以找的钱币面额分别为:50 元、20 元、10 元、5 元、1 元。首先找一个不超过 86 元的最大面额的货币,即 50 元,还

需找86-50=36元:再找一个不超过36元的最大面额的货币20元,还需找36-20=16元: 再找 个不超过16元的货币10元,还需找16-10-6元:再找一个不超过6元的货币5元, 还需找6-5=1元:再找一个不超过1元的货币1元。这样共找出50元、20元、10元、5 元、1元的货币各1张,找零所用货币总张数为5,这是货币张数最少的找零方案。 找零问题的贪心实现过程如表15-1所示。 表15-1找零的贪心过程 当前欲 当前可找 本次贪 剩余待 当前解集合 找钱数 最大币值 心找家 找我数 36 40 2 36 20 20 16 {50,201 16 10 10 450.20.10月 4 6 {50.20.10.51 51 10 45020.10.5.1} 15.1.3贪心法的基本要素 贪心法的核心问题是选择能产生问题最优解的最优度量标准,即具体的贪心策略。其 实,读者从找零问题的贪心实现过程就可以感受到,贪心策略总是做出在当前看来是最优 的选择,也就是说贪心策略并不是从整体上加以考虑,它所做出的选择只是在某种意义上 的局部最优解。而众多问题之所以可用贪心法求解,是由于这些问题自身的特性决定了能 够将贪心策略获取的局部最优解组合得到全局最优解或较优解。这些特性主要包括以下两 点: (1)贪心选择性质: 所谓贪心选择性质是指所求问题的整体最优解可以通过一系列局部最优的选择,即贪 心选择来达到。这是贪心算法可行的第一个基本要素,也是贪心算法与动态规划算法的主 要区别。在动态规划算法中,每步所做的选择往往依赖于相关子问题的解。因而只有在解 出相关子问题后,才能做出选择。而在贪心算法中,仅在当前状态下做出最好选择,即局 部最优选择。然后再去解做出这个选择后产生的相应的子间问题。贪心算法所做的贪心选择 可以依赖于以往所做过的选择,但决不依赖于将来所做的选择,也不依赖于子问题的解。 正是由于这种差别,动态规划算法通常以自底向上的方式解各子问题,而贪心算法则通常 以自顶向下的方式进行,以迭代的方式做出相继的贪心选择,每做一次贪心选择就将所求 问题简化为规模更小的子问题。 对于具体问题,要确定它是否具有贪心选择性质,必须证明每一步所做的贪心选择最 终导致问题的整体最优解。首先考察问题的一个整体最优解,并证明可修改这个最优解, 使其以贪心选择开始。做了贪心选择后,原问题简化为规模更小的类似子问题。然后,用 数学归纳法证明,通过每一步做贪心选择,最终可得到问题的整体最优解。其中,证明贪 心选择后的问题简化为规模更小的类似子问题的关键在于利用该问题的最优子结构性质。 (2)最优子结构性质 当一个问题的最优解包含其子问题的最优解时,称此问题具有最优子结构性质。运用 3
3 需找 86-50=36 元;再找一个不超过 36 元的最大面额的货币 20 元,还需找 36-20=16 元; 再找一个不超过 16 元的货币 10 元,还需找 16-10=6 元;再找一个不超过 6 元的货币 5 元, 还需找 6-5=1 元;再找一个不超过 1 元的货币 1 元。这样共找出 50 元、20 元、10 元、5 元、1 元的货币各 1 张,找零所用货币总张数为 5,这是货币张数最少的找零方案。 找零问题的贪心实现过程如表 15-1 所示。 表 15-1 找零的贪心过程 步 骤 当前欲 找钱数 当前可找 最大币值 本次贪 心找零 剩余待 找钱数 当前解集合 1 86 50 50 36 {50} 2 36 20 20 16 {50,20} 3 16 10 10 6 {50,20,10} 4 6 5 5 1 {50,20,10,5} 5 1 1 1 0 {50,20,10,5,1} 15.1.3 贪心法的基本要素 贪心法的核心问题是选择能产生问题最优解的最优度量标准,即具体的贪心策略。其 实,读者从找零问题的贪心实现过程就可以感受到,贪心策略总是做出在当前看来是最优 的选择,也就是说贪心策略并不是从整体上加以考虑,它所做出的选择只是在某种意义上 的局部最优解。而众多问题之所以可用贪心法求解,是由于这些问题自身的特性决定了能 够将贪心策略获取的局部最优解组合得到全局最优解或较优解。这些特性主要包括以下两 点: (1)贪心选择性质: 所谓贪心选择性质是指所求问题的整体最优解可以通过一系列局部最优的选择,即贪 心选择来达到。这是贪心算法可行的第一个基本要素,也是贪心算法与动态规划算法的主 要区别。在动态规划算法中,每步所做的选择往往依赖于相关子问题的解。因而只有在解 出相关子问题后,才能做出选择。而在贪心算法中,仅在当前状态下做出最好选择,即局 部最优选择。然后再去解做出这个选择后产生的相应的子问题。贪心算法所做的贪心选择 可以依赖于以往所做过的选择,但决不依赖于将来所做的选择,也不依赖于子问题的解。 正是由于这种差别,动态规划算法通常以自底向上的方式解各子问题,而贪心算法则通常 以自顶向下的方式进行,以迭代的方式做出相继的贪心选择,每做一次贪心选择就将所求 问题简化为规模更小的子问题。 对于具体问题,要确定它是否具有贪心选择性质,必须证明每一步所做的贪心选择最 终导致问题的整体最优解。首先考察问题的一个整体最优解,并证明可修改这个最优解, 使其以贪心选择开始。做了贪心选择后,原问题简化为规模更小的类似子问题。然后,用 数学归纳法证明,通过每一步做贪心选择,最终可得到问题的整体最优解。其中,证明贪 心选择后的问题简化为规模更小的类似子问题的关键在于利用该问题的最优子结构性质。 (2)最优子结构性质 当一个问题的最优解包含其子问题的最优解时,称此问题具有最优子结构性质。运用

贪心策略在每一次转化时都取得了最优解。问题的最优子结构性质是该问题可用贪心算法 或动态规划算法求解的关键特征。贪心算法的每一次操作都对结果产生直接影响,而动态 规划则不是。贪心算法对每个子问题的解决方案都做出选择,不能回退:动态规划则会根 据以前的选择结果对当前进行选择,有回退功能。 15.1.4贪心法的注意事项 一个多阶段决策问题如果具备最优子结构性质,可以考虑用贪心法来求解。但并不是 具备最优子结构性质问题,就一定就能用贪心法求解。问题需要用贪心法求解时,应该验 证贪心的正确性以及选择合适的贪心策略。 (1)贪心的正确性 要证明贪心性质的正确性,是贪心算法的真正挑战,因为并不是每次局部最优解都会 与整体最优解之间有联系,有时靠贪心算法生成的解不是最优解。这样,贪心性质的证明 就成了贪心算法正确的关键。 对某些问题来讲,贪心方法在大部分数据中都是可行的,但还必须考虑到所有可能出 现的特殊情况,以保证该贪心性质在这些特殊情况中仍然正确,否则不经证明随便的使用 贪心算法可能就会得到错误的结果。 仍然以前面提到的找零问题为例,该问题虽然满足最优子结构性质,但并不意味着该 问题一定可以用贪心策略求解。只是由于现有的1、5、10、20、50}货币体系设计, 保障了该问题的贪心可以得到最优解。但是如果货币体系设计为{1、5、8、10}、要找的零 钱是13时,用前术含心方法所找出的零钱张数是4,面值分别为10、1、1和1,然而这里 的最优策略找出的零钱张数应该是2,面值为8和5。全局最优解并不包含局部最优解,在 这种情况下,贪心法失效了。 所以贪心法虽然执行效幸高,但适用范围却并不十分广泛,使用之前需证明贪心策略 是否能够得到问题的最优解。像上述找零一样的问题,如果货币体系设计不合理,贪心法 不能保证得到问题的最优解 就不适合用贪心了,这时我们可以用本章第3节介绍的动态 规划算法来实现问题求解。 (2)贪心策略的选择 对于可以用贪心算法求解的问题,当“贪心序列”中的每项互异且当问题没有重叠性 时,看起来总能通过贪心算法取得(近似)最优解的。但我们还需要选择合适的局部最优 策略,才能够保证求解结果的正确性,比如下面的背包问题: 问题描述:有一个容量M=200的背包,还有8种物品,每种物品的体积与价值如表 表15-28种物品的体积与价售 物品 GH 体积 40 5520 65 30 40 45 35 价值 35202040 35 15 4020 单位体积价值0.880.3610.621170380.890.57 15-2所示。假设每种物品可以取任意分量,现在要将物品装进背包,要求不能超过背 4
4 贪心策略在每一次转化时都取得了最优解。问题的最优子结构性质是该问题可用贪心算法 或动态规划算法求解的关键特征。贪心算法的每一次操作都对结果产生直接影响,而动态 规划则不是。贪心算法对每个子问题的解决方案都做出选择,不能回退;动态规划则会根 据以前的选择结果对当前进行选择,有回退功能。 15.1.4 贪心法的注意事项 一个多阶段决策问题如果具备最优子结构性质,可以考虑用贪心法来求解。但并不是 具备最优子结构性质问题,就一定就能用贪心法求解。问题需要用贪心法求解时,应该验 证贪心的正确性以及选择合适的贪心策略。 (1)贪心的正确性 要证明贪心性质的正确性,是贪心算法的真正挑战,因为并不是每次局部最优解都会 与整体最优解之间有联系,有时靠贪心算法生成的解不是最优解。这样,贪心性质的证明 就成了贪心算法正确的关键。 对某些问题来讲,贪心方法在大部分数据中都是可行的,但还必须考虑到所有可能出 现的特殊情况,以保证该贪心性质在这些特殊情况中仍然正确,否则不经证明随便的使用 贪心算法可能就会得到错误的结果。 仍然以前面提到的找零问题为例,该问题虽然满足最优子结构性质,但并不意味着该 问题一定可以用贪心策略求解。只是由于现有的{1、5、10、20、50.}货币体系设计, 保障了该问题的贪心可以得到最优解。但是如果货币体系设计为{1、5、8、10}、要找的零 钱是 13 时,用前述贪心方法所找出的零钱张数是 4,面值分别为 10、1、1 和 1,然而这里 的最优策略找出的零钱张数应该是 2,面值为 8 和 5。全局最优解并不包含局部最优解,在 这种情况下,贪心法失效了。 所以贪心法虽然执行效率高,但适用范围却并不十分广泛,使用之前需证明贪心策略 是否能够得到问题的最优解。像上述找零一样的问题,如果货币体系设计不合理,贪心法 不能保证得到问题的最优解,就不适合用贪心了,这时我们可以用本章第 3 节介绍的动态 规划算法来实现问题求解。 (2)贪心策略的选择 对于可以用贪心算法求解的问题,当“贪心序列”中的每项互异且当问题没有重叠性 时,看起来总能通过贪心算法取得(近似)最优解的。但我们还需要选择合适的局部最优 策略,才能够保证求解结果的正确性,比如下面的背包问题: 问题描述:有一个容量 M=200 的背包,还有 8 种物品,每种物品的体积与价值如表 表 15-2 8 种物品的体积与价值 物品 A B C D E F G H 体积 40 55 20 65 30 40 45 35 价值 35 20 20 40 35 15 40 20 单位体积价值 0.88 0.36 1 0.62 1.17 0.38 0.89 0.57 15-2 所示。假设每种物品可以取任意分量,现在要将物品装进背包,要求不能超过背

包总容量且物品总价值最大,该如何装包? 要想总价值最大,可以试着用贪心法求解,通常大家容易想到以下的贪心策略: (1)每次取价值最大的物品: (2) 每次取体积最小的物品: (3)每次取单位体积价值最大的物品: 这三种贪心策略不同,按照这三种贪心策略进行求解,各自得到的装包方案是明显不 同,究竞哪一种是适合本问题的贪心策略呢?下面我们做一下比较。 采用第一种贪心策略,每次取价值最大的物品,贪心过程如表153所示 表15-3背包问题的贪心策略(1) 步背包当前 当前最大 本次盒心 所洗物品 己洗物品背包剩 当前解集合 可用容金 价值物品 选择物品 价值/体形 总价值 余容 1 200 D、G G 40/45 40 155 IG 2 155 4065 80 90 GD] 90 A、E 35/30 115 60 GDEI 3540 1s0 50 GDEA 5 20 B、C、H 20/20 170 0GD.E.A.C 采用第二种贪心策略,每次取体积最小的物品,贪心过程如表15-4所示: 表15-4背包问题的贪心策略(2) 步背包当前 当前最小 本次贪心所选物品己选物品背包剩 当前解集合 骤可用容量 体积物品 选择物品 价值/体积 总价值余容量 200 20/20 20 180 C 1g0 35/30 10 3 150 H 20/35 75 115 (C.E.H] 4 115 A、E A 35/40 110 75 CEHa 75 15/40 125 35 fC.EH.A.F) 6 35 G G 40/45156 0CE.H,A,F,G) 采用第三种贪心策略,每次取单位体积价值最大的物品,贪心过程如表15-5所示: 表15-5背包问题的贪心策略(3》 背句当 当前单位体本次贪心、所选物品 口洗物思 背句利 当前解集合 前容量 积最大价值 选择物品价值/体 总价值 余容量 200 E E 35/30 35 170 E 170 20/20 55 150 (EC) 3150 G G 40/45 95 115 EC.GI 4115 35/40 130 75 5 D 40/6 170 EC.GA.D 610 H H2035175.70E,C.GA.D,H田 比较一下三种贪心方案中背包里装下的物品的总价值,方案1为170,方案2为156 5
5 包总容量且物品总价值最大,该如何装包? 要想总价值最大,可以试着用贪心法求解,通常大家容易想到以下的贪心策略: (1) 每次取价值最大的物品; (2) 每次取体积最小的物品; (3) 每次取单位体积价值最大的物品; 这三种贪心策略不同,按照这三种贪心策略进行求解,各自得到的装包方案是明显不 同,究竟哪一种是适合本问题的贪心策略呢?下面我们做一下比较。 采用第一种贪心策略,每次取价值最大的物品,贪心过程如表 15-3 所示; 表 15-3 背包问题的贪心策略(1) 步 骤 背包当前 可用容量 当前最大 价值物品 本次贪心 选择物品 所选物品 价值/体积 已选物品 总价值 背包剩 余容量 当前解集合 1 200 D、G G 40/45 40 155 {G} 2 155 D D 40/65 80 90 {G,D} 3 90 A、E E 35/30 115 60 {G,D,E} 4 60 A A 35/40 150 20 {G,D,E,A} 5 20 B、C、H C 20/20 170 0 {G,D,E,A,C} 采用第二种贪心策略,每次取体积最小的物品,贪心过程如表 15-4 所示; 表 15-4 背包问题的贪心策略(2) 步 骤 背包当前 可用容量 当前最小 体积物品 本次贪心 选择物品 所选物品 价值/体积 已选物品 总价值 背包剩 余容量 当前解集合 1 200 C C 20/20 20 180 {C} 2 180 E E 35/30 55 150 {C,E} 3 150 H H 20/35 75 115 {C,E,H} 4 115 A、F A 35/40 110 75 {C,E,H,A} 5 75 F F 15/40 125 35 {C,E,H,A,F} 6 35 G G 40/45 156 0 {C,E,H,A,F,G} 采用第三种贪心策略,每次取单位体积价值最大的物品,贪心过程如表 15-5 所示; 表 15-5 背包问题的贪心策略(3) 步 骤 背包当 前容量 当前单位体 积最大价值 本次贪心 选择物品 所选物品 价值/体积 已选物品 总价值 背包剩 余容量 当前解集合 1 200 E E 35/30 35 170 {E} 2 170 C C 20/20 55 150 {E,C} 3 150 G G 40/45 95 115 {E,C,G} 4 115 A A 35/40 130 75 {E,C,G,A} 5 75 D D 40/65 170 10 {E,C,G,A,D} 6 10 H H 20/35 175.7 0 {E,C,G,A,D,H} 比较一下三种贪心方案中背包里装下的物品的总价值,方案 1 为 170,方案 2 为 156

方案3为175.7。这里只有第3种策略得到的才是符合要求的最优解。 15.2贪心法实例 15.2.1删数问题 【例15.1】删数问题。 键盘输入一个高精度的正整数n(≤100位),去掉其中任意s个数字后剩下的数字按 照原来的左右次序组成一个新的正整数。编程对给定的与s,寻找一种方案,使得剩下 的数字组成的新数最小。 比如:n=178543,s=4 则输出13,表示正整数178543,删除4位数字后得到的最小值是13。 分析: (1)由于给定的正整数有效位数是100位,用一般的整数类型(包括长整数)无法表 示,可以考虑用字符串来存储正整数。 (2)如何决定哪s位数字被删除呢?被删除的是否是最大的s个数字呢?为了尽可能 逼近目标,我们采用贪心法来解决问题,选取的贪心策略为: ①把对s个数字的删除,看成是一个逐步实现的过程,每一步删除一个数字,用s步 完成对s个数字的删除。 ②每一步总是选择一个使剩下的数最小的数字完成删除,也就是按照从高位到低位 的顺序进行搜索,如果各位数字是递增的,则刷除最后一位数字:否则,删除第一个递减 区间的首位数字。这样选择一位数字并删除后,便得到一个新的数字串 ③以后每一步回到上一步完成删除之后的数字串的串首,按照上述规则再别除下一 个数字。执行s次后,便删除了s位数字,剩余的数字串便是问题的解。 例如:n=178543,s-4时的删除过程为: 第一步:n=178543,第一个递减区间为8543,删除其首位数字8。 第二步:=17543,第一个递减区间为7543,删除其首位数字7。 第三步:n=1543,第一个递减区间为543,删除其首位数字5 第四步:=143,第一个递减区间为43,别除其首位数字4。 剩余的数字串为:13,即为所求。该问题求解的具体贪心策略如表15-6所示。 表15-6删数问题的贪心策略 步骤当前数字串第一个递减区间递减区间首位数字剩余数字串当前解集合 1 178543 8543 17543 {8 2 17543 7543 7 1543 871 1543 543 143 8.7.51 143 43 13 8.7.5.4} 根据上述思路,可以得到如下的程序: 6
6 方案 3 为 175.7。这里只有第 3 种策略得到的才是符合要求的最优解。 15.2 贪心法实例 15.2.1 删数问题 【例 15.1】 删数问题。 键盘输入一个高精度的正整数 n(≤100 位),去掉其中任意 s 个数字后剩下的数字按 照原来的左右次序组成一个新的正整数。编程对给定的 n 与 s,寻找一种方案,使得剩下 的数字组成的新数最小。 比如:n=178543,s=4 则输出 13,表示正整数 178543,删除 4 位数字后得到的最小值是 13。 分析: (1)由于给定的正整数有效位数是 100 位,用一般的整数类型(包括长整数)无法表 示,可以考虑用字符串来存储正整数。 (2)如何决定哪 s 位数字被删除呢?被删除的是否是最大的 s 个数字呢?为了尽可能 逼近目标,我们采用贪心法来解决问题,选取的贪心策略为: ① 把对 s 个数字的删除,看成是一个逐步实现的过程,每一步删除一个数字,用 s 步 完成对 s 个数字的删除。 ② 每一步总是选择一个使剩下的数最小的数字完成删除,也就是按照从高位到低位 的顺序进行搜索,如果各位数字是递增的,则删除最后一位数字;否则,删除第一个递减 区间的首位数字。这样选择一位数字并删除后,便得到一个新的数字串。 ③ 以后每一步回到上一步完成删除之后的数字串的串首,按照上述规则再删除下一 个数字。执行 s 次后,便删除了 s 位数字,剩余的数字串便是问题的解。 例如:n=178543,s=4 时的删除过程为: 第一步:n=178543,第一个递减区间为 8543,删除其首位数字 8。 第二步:n=17543,第一个递减区间为 7543,删除其首位数字 7。 第三步:n=1543,第一个递减区间为 543,删除其首位数字 5。 第四步:n=143,第一个递减区间为 43,删除其首位数字 4。 剩余的数字串为:13,即为所求。该问题求解的具体贪心策略如表 15-6 所示。 表 15-6 删数问题的贪心策略 步骤 当前数字串 第一个递减区间 递减区间首位数字 剩余数字串 当前解集合 1 178543 8543 8 17543 {8} 2 17543 7543 7 1543 {8,7} 3 1543 543 5 143 {8,7,5} 4 143 43 4 13 {8,7,5,4} 根据上述思路,可以得到如下的程序:

#include "stdio.h" #include "string.h" int main() int i,s,len char a[100] scanf ("&s",a); scanf ("d",&s) wthile(s>0) =0: =strlen(a) while(i<len s6a[i]<-a[i+i]) 1++ while(i<len) a[i]=a[i+1]: 1++: 1 printf("8s\n",a) return 0 以上程序在运行期间,有时会遇上一点小小的问题,即删除过程可能会使原来包含0 的数字串变成以若干个0开始的序列,比如80056,在第一次删除8之后,会变成0056, 高位的0是无效的,请读者对上述程序做一点改进,当遇到数字串首位是0的情况时,把 高位的数字0去掉。 15.2.2活动选择问题 【例15.2】事件序列问题一一活动选择问题。 己知N=12个事件的发生时刻和结束时刻(见表15-7,其中事件已经按结束时刻升序 排序)。一些在时间上没有重叠的事件,可以构成一个事件序列,如事件2、8和10,可以 写成序列{2,8,10;。事件序列包含的事件数目,称为事件序列的长度。请编程找出一个 最长的事件序列。 表15-7事件序列时刻表 事件1编号 0#1#2#34#5#67#8#910#11 发生时 1303256 4108 1515 结束时刻 3478910121415181920 分析: 7
7 #include "stdio.h" #include "string.h" int main() { int i,s,len; char a[100]; scanf("%s",a); scanf("%d",&s); while(s>0) { i=0; len=strlen(a); while(i<len &&a[i]<=a[i+1]) i++; while(i<len) { a[i]=a[i+1]; i++; } s-; } printf("%s\n",a); return 0; } 以上程序在运行期间,有时会遇上一点小小的问题,即删除过程可能会使原来包含 0 的数字串变成以若干个 0 开始的序列,比如 80056,在第一次删除 8 之后,会变成 0056, 高位的 0 是无效的,请读者对上述程序做一点改进,当遇到数字串首位是 0 的情况时,把 高位的数字 0 去掉。 15.2.2 活动选择问题 【例 15.2】 事件序列问题——活动选择问题。 已知 N=12 个事件的发生时刻和结束时刻(见表 15-7,其中事件已经按结束时刻升序 排序)。一些在时间上没有重叠的事件,可以构成一个事件序列,如事件 2、8 和 10,可以 写成序列{2,8,10}。事件序列包含的事件数目,称为事件序列的长度。请编程找出一个 最长的事件序列。 表 15-7 事件序列时刻表 事件 i 编号 0# 1# 2# 3# 4# 5# 6# 7# 8# 9# 10# 11# 发生时刻 1 3 0 3 2 5 6 4 10 8 15 15 结束时刻 3 4 7 8 9 10 12 14 15 18 19 20 分析:

根据各事件的发生时刻和结束时刻画出图来,重叠情况就一目了然,如图15.1所示。 从图15.1上可以很清楚的看出哪些事件在时间上没有重叠,从而找到一些事件序列, 例如2,8,10;。但这个序列不是最长的,序列0,1,7,10;就更长一些。应该如何选择 事件,可以保证得到最长的事件序列呢? 为了叙述方便,用begin的表示事件i的发生时刻,用cnd回表示事件i的结束时刻。 下面对事件在时间上不重叠的条件进行分析。 从图15.1很容易看出,如果两个事件a、b(a<b)在时间上没有重叠,那么有enda≤ begin[b]:如果满足这个条件,则两个事件在时间上一定没有重叠,这是一个充分必要 条件。 对于3个事件a、b、c(a<b<c),如果a和b,b和c在时间上都没有重叠,则有: end[a]sbegin[b],end[b]sbegin[e] 由于每一件事件的开始时刻一定小于结束时刻,则begin)<end回,则有: begin[a]<end[a]<begin[b]<end[b]sbegin[c]<end[c] 这个条件说明a和c在时间上也没有重叠。反之,如果3个事件a、b、c满足这个条 件,则它们在时间上没有重叠。 ● ●0并 ●一●1# ●2# ●3 5# ● ●6 ●7 ●8 ● ●9啡 ◆10# 1 ● LL上LLLL⊥⊥」 01234567891011121314151617181920 15.1各事件在时间上的占用情况图 依此类推,原题的要求其实就是找到一个最长的序列a1<a<<aa,满足: begin[a]<end[a]<begin[az]<end[az]<begin[an]<end[ao] 如果事件a、b、c满足:cndb≤begin[c],即b与c在时间上不重叠:且 a<b 则根据题意有: end[a]<end[b] 8
8 根据各事件的发生时刻和结束时刻画出图来,重叠情况就一目了然,如图 15.1 所示。 从图 15.1 上可以很清楚的看出哪些事件在时间上没有重叠,从而找到一些事件序列, 例如{2,8,10}。但这个序列不是最长的,序列{0,1,7,10}就更长一些。应该如何选择 事件,可以保证得到最长的事件序列呢? 为了叙述方便,用 begin[i]表示事件 i 的发生时刻,用 end[i]表示事件 i 的结束时刻。 下面对事件在时间上不重叠的条件进行分析。 从图 15.1 很容易看出,如果两个事件 a、b(a<b)在时间上没有重叠,那么有 end[a]≤ begin[b];如果满足这个条件,则两个事件在时间上一定没有重叠,这是一个充分必要 条件。 对于 3 个事件 a、b、c(a<b<c),如果 a 和 b,b 和 c 在时间上都没有重叠,则有: end[a] ≤begin[b],end[b] ≤begin[c] 由于每一件事件的开始时刻一定小于结束时刻,则 begin[i] <end[i],则有: begin[a] <end[a]≤begin[b] <end[b]≤begin[c] <end[c] 这个条件说明 a 和 c 在时间上也没有重叠。反之,如果 3 个事件 a、b、c 满足这个条 件,则它们在时间上没有重叠。 0 1 2 3 4 5 6 7 3 8 9 10 11 12 13 14 15 16 17 18 19 20 0# 1# 2# 3# 4# 5# 6# 7# 8# 9# 11# 10# 图 15.1 各事件在时间上的占用情况图 依此类推,原题的要求其实就是找到一个最长的序列 a1<a2<.<an,满足: begin[a1] <end[a1]≤begin[a2] <end[a2]≤.≤begin[an]<end[an] 如果事件 a、b、c 满足:end[b]≤begin[c],即 b 与 c 在时间上不重叠;且 a<b 则根据题意有: end[a]≤end[b]

因此,endfa]sbegin[,即a与c在时间上也不重叠。可以推知,如果在可能的事件 a1=timestart) 9
9 因此,end[a]≤begin[c],即 a 与 c 在时间上也不重叠。可以推知,如果在可能的事件 a1=timestart) {

select【i】-1: timestart=end[i】; outputresult(select,N); return 0; 15.2.3区间覆盖问题 【例15.3】区间覆盖问题。 用i米表示x坐标轴上坐标为-1,门的长度为1的区间,并给出M(1≤M≤200)个不 同的整数,表示M个这样的区间。现在要求画几条线段覆盖住所有的区间,条件是:每条 线段可以任意长,但是要求所画线段的长度之和最小,并且线段的数目不超过N(1≤N≤ 50) 例如,给出M=5,整数1、3、4、8和11表示区间,要求所用线段不超过N=3条,如 图15.2所示。 在图15.2所示的4个覆盖方案(1、2、B和4)中,方案4是最佳的,线段的总长 度为6。事实上方案4正是这个任务的答案。 分析: 设置一个整型数组position[M来表示所有的区间,假设position[M]已经按从小到大的 顺序排好。 4: ●●8 8 3 4 Total M-5 0 12 3456789101 图152区间覆盖图例 如果N≥M,那么用M条长度为1的线段可以覆盖住所有的区间,所求的线段总长 为M 如果N1,可以按照如下的贪心策略来解决:如果N=1,即要用一一条线 段覆盖住所有区间,很显然所需线段总长即为position[M-1-position+1:如果N-2,即 10
10 select[i]=1; timestart=end[i]; } i++; } outputresult(select,N); return 0; } 15.2.3 区间覆盖问题 【例 15.3】 区间覆盖问题。 用 i 来表示 x 坐标轴上坐标为[i–1,i]的长度为 1 的区间,并给出 M(1≤M≤200)个不 同的整数,表示 M 个这样的区间。现在要求画几条线段覆盖住所有的区间,条件是:每条 线段可以任意长,但是要求所画线段的长度之和最小,并且线段的数目不超过 N(1≤N≤ 50)。 例如,给出 M=5,整数 1、3、4、8 和 11 表示区间,要求所用线段不超过 N=3 条,如 图 15.2 所示。 在图 15.2 所示的 4 个覆盖方案(f1、f2、f3 和 f4)中,方案 f4 是最佳的,线段的总长 度为 6。事实上方案 f4 正是这个任务的答案。 分析: 设置一个整型数组 position[M]来表示所有的区间,假设 position[M]已经按从小到大的 顺序排好。 8 11 1 3 4 f1: f2: f3: f4: 0 1 2 3 4 5 6 7 8 9 10 11 M=5 6 7 8 8 Total length 图 15.2 区间覆盖图例 如果 N≥M,那么用 M 条长度为 1 的线段可以覆盖住所有的区间,所求的线段总长 为 M。 如果 N1,可以按照如下的贪心策略来解决:如果 N=1,即要用一条线 段覆盖住所有区间,很显然所需线段总长即为 position[M–1]–position[0]+1;如果 N=2,即