Greedy Algorithm
Table of Contents
1. Greedy algorithm 简介
A greedy algorithm is an algorithm that follows the problem solving heuristic of making the locally optimal choice at each stage with the hope of finding a global optimum.
贪心算法并不一定得到整体最优解,有时得到的仅是一个局部最优解。所以我们需要证明贪心算法的正确性。
参考:
Introduction to Algorithms, 3rd Edition. Chapter 16 Greedy Algorithms
"Greedy Exchange": a technique used in proving the correctness of greedy algorithms
Guide to Greedy Algorithms
2. 实例:活动选择问题
问题描述: 有很多活动需要以独占的方式使用某一公共资源(如教室),该资源一次只能被一个活动占用。怎样从活动集合中选择活动,使公共资源能服务的活动数量最多?
设活动组成的集合为
例如,考虑下面的活动集合:
对于这个例子,子集
上面例子的结果已提前给出,但如何解决这个问题呢?请看下面内容。
参考:Introduction to Algorithms, 3rd Edition. Section 16.1 An activity-selection problem
2.1. 解法 1:动态规划
我们容易验证这个问题具有最优子结构性质。为了寻找最优子结构,定义如下的集合:
显然,
假设
上面递归式假设
到此为止,已经得到一个递归解法,可根据这个递归式直接编程即可实现(运行效率低)。也可设计一个带备忘机制的递归算法,或者设计一个表格化的、自底向上基于前面递归式的动态规化算法。具体的编程实现略。
2.2. 解法 2:贪心算法
直观上,我们应该选择这样一个活动,选出它后剩下的资源应能被尽量多的其他活动所用。
现在考虑可选的活动,其中必然有一个最先结束。 所以我们应该每次选择
采用上面的贪心选择算法,由于
要证明它的正确性,只需要证明我们的第一个选择,即第一个最早结束的活动
说明:不能采用这样的贪心策略:“每次选择
+-----+--------+--------+ +-------+ | | | | | | 1--2--3--4--5--6--7--8--9--10--11--12 | | | | | | +--+ +-----+ +---+
2.2.1. 活动选择问题贪心算法实现
/* 贪心算法解决活动选择问题 */ #include<stdio.h> void greedy_activity(int s[], int f[], int n) { int i = 1; // 由于约定f按单调递增的顺序排列,所以可以总是选择a1。 printf("The selected activities are: a%d ", i); int j; for (j=1; j<n; j++) { if (s[j] >= f[i]) { printf("a%d ", j+1); i = j; } } printf("\n"); } int main() { int s[] = {1, 3, 0, 5, 3, 5, 6, 8, 8, 2, 12}; // 活动开始时间 int f[] = {4, 5, 6, 7, 9, 9, 10, 11, 12, 14, 16}; // 活动结束时间 int n = sizeof(s)/sizeof(s[0]); greedy_activity(s, f, n); return 0; }
运行上面程序,会输出:“The selected activities are: a1 a4 a8 a11 ”
3. 实例:单源最短路径(Dijkstra 算法)
4. 实例:最小生成树
5. 实例:背包问题
背包问题(Knapsack problem)描述如下:假设我们有
Figure 1: 背包问题(摘自其 Wikipedia)
这个问题根据不同的情况可以归结为不同的解决方法。假定我们选取的物品每个都是独立的,不能选取部分。也就是说我们要么选取某个物品,要么不选取它,不能只选取一个物品的一部分,这种情况,我们称之为 0-1 背包问题(0-1 knapsack problem)。而如果我们可以选取部分物品的话,这个问题则成为部分背包问题(fractional knapsack problem)。
5.1. 0-1 knapsack problem(不能用贪心算法)
要使得背包里装的物品价值最大,那我们贪心地每次选取那些单位价值最高(即
我们需要换一种思路。假设问题的解为物品
上面的推理表明“0-1 背包问题的最优解中包含其子问题的最优解”。具有这个特点的问题往往可以用“动态规划”来解决。
基于上面的推理,下面介绍使用“动态规划”解决 0-1 背包问题的过程。我们定义 函数
显然,有下面推导关系:
下面的伪代码可以解决 0-1 背包问题:
// pseudo code for 0-1 knapsack problem // Input: // Values (stored in array v) // Weights (stored in array w) // Number of distinct items (n) // Knapsack capacity (W) for j from 0 to W do: m[0, j] := 0 for i from 1 to n do: for j from 0 to W do: if w[i] > j then: m[i, j] := m[i-1, j] else: m[i, j] := max(m[i-1, j], m[i-1, j-w[i]] + v[i])
算法的时间复杂度为
参考:https://en.wikipedia.org/wiki/Knapsack_problem#0/1_knapsack_problem
5.2. fractional knapsack problem(贪心算法)
可以用贪心算法解决“部分背包问题”。贪心策略是: 优先选取单位价值最高的物品,直到达到背包的重量限制。