26 贪心算法

贪婪虽然不是一种好行为,但确是一种不错的算法

大纲

  • 营养搭配问题

  • 贪心算法求解

  • 换硬币问题

营养搭配问题

根据前面学到的集合知识,下面我们想解决一个营养搭配的问题,假设我们有几种蔬菜水果可以选择,每种果蔬各自包含不同的营养成份,如果我们需要一个营养组成,如何选择最少的果蔬,使得这些营养都得到。

假设我们一共需要8种营养成份,这些信息我们存放在一个叫做nutrients的集合中。

nutrients = set(['a','b','c','d','e','f','g','h'])

然后用一个字典来存放7种果蔬和对应的营养成份。问题就是如何来选择最少的果蔬组合,使其营养成份达到标准。

foods = dict()
foods['香蕉'] = set(['a','c'])
foods['苹果'] = set(['b','c'])
foods['李子'] = set(['a','c','h'])
foods['西瓜'] = set(['d','e'])
foods['白菜'] = set(['f','h'])
foods['胡萝卜'] = set(['d','g'])
foods['西红柿'] = set(['c','d','e'])

一种自然的思路是使用暴力求解的方法,也就是我们穷举出所有可能的果蔬组合,对每一种组合来看营养成份是否达到。那我们就加载导入之前学到的combinations函数。

下面是所有2元素构成的组合。

因为不清楚一个组合中2个元素的食物是否够,所以会去遍历所有k个元素的食物组合,这个k从2到7。下面来定义一个穷举的函数。

在上面的函数中,我们定义了一个双重循环,外层循环是对所有的果蔬组合进行遍历,内层循环是对某个组合中的所有元素进行遍历。将果蔬组合的营养成分进行合并,如果需要的营养成份是组合的营养成份的子集,我们就返回这个果蔬组合。函数的输入是可选择的果蔬食物,需要的营养,和组合参数k。

然后我们用一个循环来显示出所有可能的组合参数,我们发现在5种以下的果蔬组合是不可能达到营养标准的,所以最少是5种食物的组合,发现其中有4个组合是营养达标的。

贪心算法求解

另一种巧妙的思路是使用贪婪方法,又称为贪心算法。其基本思想是,只看当前这一步,如何能使这当前一步的操作,最大化的满足需求。回到果蔬组合的例子中,我们先遍历所有的果蔬,看哪一种果蔬可以覆盖最多的营养,然后再看剩下的果蔬中,哪一种可以覆盖剩下未覆盖的营养成份。以此类推。

小结:从这个例子可以对比贪心算法的答案和穷举的答案,可以看到贪心算法只提供了一种解答,而穷举得到了所有四种解答。不过,贪心算法的速度会比穷举快很多。

换硬币问题

贪心算法另一种经典应用是换硬币问题,如果我们手上有一定金额的钱,需要换成硬币,如何使用最少的硬币来换。

先用列表来定义可用的硬币面值和硬币数量,再定义需要换钱的金额是13.2元。

用一个字典来保存换硬币的面值和量,存在change里。基于贪心算法,也从面值最大的硬币换起,看最大面值的硬币能换多少个。如果需要硬币数量超过能提供的硬币个数,则将n变为能提供的硬币个数。最后一步是关键,需要将原来的金额money进行修改,改成前面换硬币后剩下的金额,使用减法进行了更新。

Last updated