第四部分 高级设计和分析技术
第16章 贪心算法
贪心算法在每一步都做出当时看起来最佳的选择,它总是做出局部最优的选择,寄希望这样的选择能导致全局最优解。
1. 活动选择问题
有一个需要使用每个资源的n个活动组成的集合S= {a1,a2,···,an },资源每次只能由一个活动使用。每个活动ai都有一个开始时间si和结束时间fi,且 0≤ si < fi <∞ 。一旦被选择后,活动ai就占据半开时间区间[si,fi)。如果[si,fi]和[sj,fj]互不重叠,则称ai和aj两个活动是兼容的。该问题就是要找出一个由互相兼容的活动组成的最大子集。例如下图所示的活动集合S,其中各项活动按照结束时间单调递增排序。
从图中可以看出S中共有11个活动,最大的相互兼容的活动子集为:{a1,a4,a8,a11,}和{a2,a4,a9,a11}。
递归贪心算法
贪心算法通常采用自顶向下的设计,因为不需要做出过多的选择而求解所有的子问题。
Recursive-Activity-Selector(s, f, k, n)
m = k + 1
// find the first activity in Sk to finish
while m <= n and s[m] < f[k]
m = m + 1
if m <= n
return {am} U Recursive-Activity-Selector(s, f, m, n)
else
return Ø
### 迭代贪心算法
Greedy-Activity-Selector(s, f)
n = s.length
A = {a1}
k = 1
for m = 2 to n
if s[m] >= f[k]
A = A U {am}
k = m
return A
2. 贪心算法原理
贪心选择性质
第一个关键要素是该性质:我们可以通过做出局部最优(贪心)选择来构造全局最优解。换句话说,当进行选择时,我们直接做出在当前问题中看来最优的选择,而不必考虑子问题的解。
在动态规划方法中,每个步骤都要进行一次选择,但选择通过依赖于子问题的解。因此,我们通常以一种自底向上的方式来解动态规划问题,先求解较小的子问题,然后是较大的子问题。
贪心算法中,总是做出当时看来最佳的选择,然后求解剩下的唯一的子问题。
最优子结构
如果一个问题的最优解包含其子问题的最优解,是称此问题具有最优子结构性质。当应用于贪心算法时,我们通常使用更为直接的最优子结构。我们可以假定,通过对原问题应用贪心选择即可得到子问题。我们真正要做的全部工作就是论证:将子问题的最优解与贪心选择组合在一起就能生成原问题的最优解。
3. 赫夫曼编码
讨论赫夫曼编码问题,赫夫曼编码的思想就是变长编码。变长编码就是让字符表中出现概率高的字符的编码长度尽可能小,而出现概率高的字符的编码长度相对较长。然后还要遵循前缀码的要求,就是任意一个编码都不是其他编码的前缀码,这样方便解码。
构造赫夫曼编码
Huffman(C)
n = |C|
Q = C
for i = 1 to n
allocate a new node z
z.left = x = Extract-Min(Q)
z.right = y = Extract-Min(Q)
z.freq = x.freq + y.freq
Insert(Q, z)
return Extract-Min(Q) // return the root of the tree