前言
适用于最优化问题的算法往往包含一系列步骤,每个步骤都面临多种选择。使用动态规划解决最优化问题,相当于计算出每咱选择,浪费大量效率。对于“特定”下的最优化问题,可以使用更简单更高效的贪心算法。贪心算法在每一步的选择中,都选择当时最佳的情况。即局部最优的选择->全局最优 思想。
活动选择问题
动态规划解法
c[i, j]为
S
i
j
S_{ij}
Sij中最大兼容子集中的活动数
将动态规划解转化为贪心解
选择一个活动使得选出它后剩下的资源能被尽量多的其他任务所用。根据直觉,我们首选的活动应该是S中最早结束的活动。
根据上述证明结论,根据结速时间重新排序活动集合S,迭代贪心算法
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
练习
1-1根据递归式(16.2)为活动选择问题设计一个动态规划算法。算法应该按排好前文定义计算最大兼容活动集的大小c[i,j]并生成最大集本身。假定输入的活动已按公式(16.1)排序。比较你的算法和GREEDY-ACTIVITY-SELECTOR的运行时间
DYNAMIC-ACTIVITY-SELECTOR(s, f, n)
let c[0..n + 1, 0..n + 1] and act[0..n + 1, 0..n + 1] be new tables
for i = 0 to n
c[i, i] = 0
c[i, i + 1] = 0
c[n + 1, n + 1] = 0
for l = 2 to n + 1
for i = 0 to n - l + 1
j = i + l
c[i, j] = 0
k = j - 1
while f[i] < f[k]
if f[i] ≤ s[k] and f[k] ≤ s[j] and c[i, k] + c[k, j] + 1 > c[i, j]
c[i, j] = c[i, k] + c[k, j] + 1
act[i, j] = k
k = k - 1
print "A maximum size set of mutually compatible activities has size" c[0, n + 1]
print "The set contains"
PRINT-ACTIVITIES(c, act, 0, n + 1)
PRINT-ACTIVITIES(c, act, i, j)
if c[i, j] > 0
k = act[i, j]
print k
PRINT-ACTIVITIES(c, act, i, k)
PRINT-ACTIVITIES(c, act, k, j)
- GREEDY-ACTIVITY-SELECTOR 运行时间为Θ(n)
- DYNAMIC-ACTIVITY-SELECTOR 运行时间为O(n^3)
1-2假定我们不再一直选择最早结束的活动,而是选择最晚开始的活动,前提仍然是与之前选出的所有活动均兼容。描述如何利用这一方法设计贪心算法,并证明算法会产生最优解。
反向计算,先找出开始时间最晚的活动,然后再找出结束时间早于开始时间且开始时间最晚的活动。
1-3对于活动选择问题,并不是所有贪心算法都能得到最大兼容活动子集。请举例说明,在剩余兼容活动中选择持续时间最短者不能得到最大集。类似的,说明在剩余兼容活动中选择与其他剩余活动重叠最少者,以及选择最早开始者均不能得到最优解。
(略)
1-4假定有一组活动,我们需要将它们安排到一些教室,任意活动都可以在任意教室进行。我们希望使用最少的教室完成所有活动。设计一个高效的贪心算法求每个活动应该在哪个教室进行。
(这个问题称为区间图着色问题(interval-graph color problem)。我们可以构造一个区间图,顶点表示给定的活动,边连接不兼容的活动。要求用最少的颜色对顶点进行着色,使得所有相邻顶点颜色均不相同——这与使用最少的教室完成活动的问题是对应的)
我们很容易就可以想到用GREEDY-ACTIVITY-SELECTOR(s, f)来解决这个问题,首先调用这个函数,得到可以兼容的最大活动数,然后再在余下的活动中再次调用这个函数,直至活动为0。
对于区间图着色(interval-graph coloring)问题,先在一个集合中放入一个点,然后把不与这个点相邻的所有元素(这些元素也应该互不相邻吧)放入这个集合,对于剩下的点,重复前面的动作即可,依此循环,直至没有点可选。最后,有多少个集合就是多少种颜色,集合中的元素用相同的色渲染。
1-5考虑活动选择问题的一个变形:每个活动ai除了开始和结束时间外,还有一个值vi。目标不再是求规模最大的兼容活动子集,而是求值之和最大的兼容活动子集。也就是说,选择一个兼容活动子集A,使得 ∑ a k ∈ A v k \sum_{a_{k}\in A}v_k ∑ak∈Avk最大化。设计一个多项式时间算法求解此问题。
令S[i,j]表示在活动a[i]结束之后开始,且在a[j]开始之前结束的那些活动。考虑S[i,j]之中的一个活动a[k],则S[i,j] = S[i,k] + a[k] + S[k,j],动态规划可解.
也可使用贪心:
- 对活动根据结束排序 a0,a1…an-1
- 根据开始时间取a0放入,计算出单位时间权值
- 现在取a1放入,如果a0和a1有重合,则比较 a0和a1单位时间权值
- 依此类推
贪心算法原理
一般情况下,我们可以按如下步骤设计贪心算法:
- 将最优化问题转换为这样的形式:对其做出一些选择后,只剩下一个子问题需要求解。
- 证明做出贪心选择后原问题总是存在最优解,即贪心选择总是安全的。
- 证明做出贪心选择后剩余的子问题满足性质:其最优解与贪心选择组合即可得到原问题的最优解,这样就得到了最优子结构。
贪心选择性质
一个全局最优解可以通过局部最优选择(贪心选择)达到, 不考虑子问题的结果;
- 动态规划每一步都要做出选择,并且选择依赖于子问题的解(先求解出子问题的解,对解进行选择),所以动态规划要自底向上, 从小问题到大问题;
- 在贪心算法中,我们总是做出当时看来最佳的选择,然后求解剩下的唯一的子问题。贪心算法进行选择时可能依赖之前做出的选择,但不依赖任何将来的选择或是子问题的解。因此贪心算法是自顶向下;
最优子结构
如果一个问题的最优解包含其子问题的最优解,则称此问题具有最优子结构性质。
- 0-1 背包问题:一个小偷发现了n个商品,每件商品的价格和重量已知,小偷的背包只能容纳重量为W,小偷需要带走尽可能高价值的商品,每件商品只能整个带走而不能只带走一部分。(采用动态规划)
- 分数背包问题:与0-1背包不同的是小偷可以只带走商品的部分而不必全部带走(采用贪心策略)
01背包和分数背包都有最优子结构性质, 但01背包不具有贪心选择性
练习
2-1证明:分数背包问题具有贪心选择性
- 证明命题:设𝑎𝑘是剩余物品中𝑣𝑖/𝑤𝑖 最大的物品,则对于这个子问题,𝑎𝑘必在最优解中。
- 假设存在𝑎𝑗(𝑤𝑗≤𝑤𝑘)使得∑𝑣𝑖更大
- 由于𝑣𝑘/𝑤𝑘≥𝑣𝑗/𝑤𝑘 所以𝑣𝑘≥𝑣𝑗
- 假设不成立
2-2设计动态规划求解0-1背包问题,要求时间为O(nW),n为商品数量,W是小偷能放进背包的最大商品总质量。
0-1-KNAPSACK(n, W)
Initialize an (n + 1) by (W + 1) table K
for i = 1 to n
K[i, 0] = 0
for j = 1 to W
K[0, j] = 0
for i = 1 to n
for j = 1 to W
if j < i.weight
K[i, j] = K[i - 1, j]
else
K[i, j] = max(K[i - 1, j], K[i - 1, j - i.weight] + i.value)
2-3假定在0-1背包问题中,商品的重量递增序与价值递减序完全一样。设计一个高效的算法求解背包问题的变形的最优解,证明你的算法是正确的。
可用贪心, v1>v2,w1<w2;符合贪心选择性
2-[4-5]
(略)
2-6说明如何在O(n)时间内解决部分背包问题
使用线性时间的中位数算法。即不一定要知道具体排序,只要前几物品重量满足就行
2-7给定两个集合A和B,各包含n个正整数。你可以按需要任意重排每个集合。重排后,令ai为集合A的第i个元素,bi为集合B的第i个元素。于是你得到回报 ∏ i = 1 n a i b i \prod_{i=1}^{n}a_{i}^{b_{i}} ∏i=1naibi。设计算法最大化你的回报。证明你的算法是正确的,并分析运行时间。
对集合A和B进行排序,每次取最大值,计算a^b.
赫夫曼编码
可变编码比固定编码好的多,其特点是对高频字符赋以短编码
(45 * 1 + 13 * 3 + 12 * 3 + 16 * 3 + 9 * 4 + 5 * 4)* 1000 = 224000
前缀编码
让了字符编码成为另一字符编码的前缀。
构造哈夫曼编码
哈夫曼设计了一种可用来构造哈夫曼编码的最优前缀码的贪心算法。
n=|C|
Q=C
for i=1 to n-1
allocate a new node z
z.left=x=EXTRACT-MIN(Q)//寻找Q中频率最低的数作为左孩子
z.right=y=EXTRACT-MIN(Q)
z.freq=x.freq+y.freq
INSERT(Q,z)
return EXTRACT-MIN(Q)
- a图集合: 5,9,12,13,16,45
- b图集合:12,13,14,16,45 (5,9(最小)移出生成子树,其树根14加入集合)
- c图集合:14,16,25,45 (12,13(最小)移出生成子树,其树根25加入集合)
- d图集合:25, 30, 45 (14,16(最小)移出生成子树,其树根30加入集合)
- e图集合:45, 55 (25,30(最小)移出生成子树,其树根55加入集合)
- f图集合:(45,55(最小)移出生成子树,其树根100加入集合)
哈夫曼算法的正确性
要证明哈夫曼算法的正确性,只要证明最优前缀码问题具有贪心选择性质和最优子结构性质
-
贪心选择性质:全局最优解可以通过局部最优选择(贪心选择)达到
-
最优子结构性质:子问题的最优解和贪心选出来的解可以凑成原问题的最优解
练习
贪心算法的理论基础
贪心算法在确定何时能够产生最优解的时候,其实用到一种叫做拟阵的组合结构。贪心算法不一定产生最优解,但是如果一个贪心问题可以转化为拟阵,这个贪心算法可以产生最优解;
拟阵
一个拟阵是满足下列条件的一个序对M= (S,e);
- S 是一个有穷非空集合;
- e是S的一个非空子集族,称为S的独立子集;具有遗传性质, 如果A是B的子集, 并且B是e的一个成员,则A是e的一个成员;
- 拟阵具有交换性:如果A是e的成员, B是e的成员, |A|<|B|, 则有某个元素x 是(B-A), 使得A+{x} 是e的成员;
简单的拟阵实例
M=(S,L)
S={1,2,3,5}
L={ {1},{3},{5},{1,3},{1,5},{3,5},{1,3,5} }
对拟阵M=(S,L)给出以下定义:
- 若A∈L,那么称 A 为独立集
- 对于独立集 A ,若存在 x∈S ,满足 x∉A 且A∪{ x }∈L,那么称 A 为可拓展的。若独立集 A 不可拓展,那么称 A 为极大独立集。
定理——同一拟阵的极大独立集元素个数(即大小)相同
证明:
反证法。设 A与 B 为拟阵的两个大小不同的极大独立集,不妨设|A|<|B|,那么根据拟阵的交换性,A 是可拓展的,不满足最大独立集的定义,与假设矛盾,故命题成立。
关于加权拟阵的贪心算法
对拟阵 M=(S,L) ,我们对 S 内的每一个元素赋予一个正整数权值 w(A) ("整数"这里只是规定,事实上我们在解决问题时可以进行融会贯通) , 定义 S 的子集 A 的权值 w(A) = ∑ x ∈ A w ( x ) \sum_{x\in A} w(x) ∑x∈Aw(x),即其元素权值和。
我们规定权值最大的独立集为权值最大独立集,显然权值最大独立集一定是极大独立集。
拟阵解决贪心问题的规律便在于此——对于任何能够转换为拟M=(S,L)的问题,都可以通过如下算法解决(即求出权值最大独立集):
## 给出拟阵M与权值函数w (Set这里意为返回一个集合)
Set Solve(M,w)
{
清空A;
将S按w(x)的大小降序排好;
for(对每一个x∈S,按w(x)降序)
{
if(A∪{x}∈L) ## 判断独立集
A=A∪{x};
}
return A;//即权值最大独立集
}
事实上,在将问题转化为拟阵后,套用该算法的关键便在于如何判断独立集.
最小生成树问题
在最小生成树问题中, 已知无向图 G= (V , E) 【注 : V 是点集,E 是边集】,一个长度函数w, w(e) 表示边e的长度
我们定义这样一个M=(S,L):
- S=E
- L={ x | x⊆E 且图 G’=(V,x) 中无环 }
- 这个 M 显然满足子集系统的前两个条件。
这里证明其具有遗传性与交换性:
- 遗传性:对任意A∈L, B⊆A , 显然B⊆E。假设 B 中有环,那么 A 中也一定有环,与 A∈L 矛盾,故 B 中无环,因此B∈L , M具有遗传性。
- 交换性:对任意A∈L,B∈L,设|A|<|B|,显然 [公式] =(V,A) 中有|V|-|A| 个连通分量 , [公式] =(V,B) 中有|V|-|B|个连通分量。
由|A|<|B| 有 |V|-|A|>|V|-|B| , 可知此时 B 中一定存在一条边 x 连接了 G_A 中的两个不同的连通分量。显然,G’=(V,A∪{x})中无环且A∪{x}∈L ,因此M具有交换性。
综上,M是一个拟阵。我们需要设计权值函数 w(x) 了,
最小生成树中是最小的权值优先
我们可以先将权值取相反数,再统一加上一个足够大的数使得权值为正即可,因此,我们使元素的权值为 w(x)=x 的边权的相反数+(E中最大边权+1),此时便满足 w(x)>0了,
练习
(略)
一个调度问题
单个处理器对若干个单位时间任务进行最优调度, 其中每个任务都有一个截止期限和超时惩罚。
单个处理器上具有期限和惩罚的单位时间任务调度的输入如下,有三个数组:
- 包含有n个单位时间任务的集合S = {a1, a2, a3, …, an};
- n个整数值的期限d1, d2, …, dn, 即任务ai要求在di之前完成;
- n个非负的权值(惩罚代价)w1, w2, w3,…, wn;如果ai在di之前没有被调度,就会有惩罚wi;
目标:找出一个S的调度,使得总的惩罚最小;
令M=(S,L)
- S 即所给集合
- L={ x | x⊆S , 且存在对 x 的合理调度使总惩罚为 0 }
- 这个 M 满足子集系统的前两个条件
证明其具有遗传性与交换性即可。
- 遗传性:对任意A∈L, B⊆A , 显然B⊆S。因为 A∈L ,所以减少了部分任务的 B 也一定存在合理的调度使得总惩罚为0, 故M具有遗传性。
- 交换性:对任意A∈L,B∈L,设|A|<|B|,显然 A 中存在任务 x 满足x∈B,x∉A ,我们取其中完成期限最靠后的一个任务 x’ 。
令集合P = A ∩ \cap ∩ B, 我们便令A’= A-P ,B’= A-P,显然|A’|<|B’|,由 B∈L 我们知道在 x’ 完成期限之前至少可以合理调度|B’|个任务,又因为|B’|>|A’|,所以在 A’ 的合理调度中一定存在位于 x’ 完成期限前的空余时间,在此安排 x’ 一定不会增加新的惩罚。
因此A∪{x’}∈L , M 具有交换性。
如何设计权值函数呢?在这里我们令元素的权值w(x)=
w
x
w_x
wx ,即惩罚。
在这样设计权值函数之后,我们可以先把所有任务没有完成的的惩罚统计出来。再计算上述拟阵的权值最大独立集(即最多免受的惩罚),两者相减即是答案。
这里的贪心策略是:对于任务ai, 其截止时间是d[i], 贪心策略是尽量将任务ai安排在距离截止时间最近的空闲时间片;(这样将更早的时间片留给更需要的任务)
如何判断独立集.即贪心策略
练习
5-1
- 根据贪心策略,安排76543的任务
- 任务1,2安排不了,即罚款总值w1+w2= 30
5-2
IS-INDEPENDENT(A)
n = A.length
let Nts[0..n] be an array filled with 0s
for each a in A
if a.deadline >= n
Nts[n] = Nts[n] + 1
else
Nts[d] = Nts[d] + 1
for i = 1 to n
Nts[i] = Nts[i] + Nts[i - 1]
// at this moment, Nts[i] holds value of N_i(A)
for i = 1 to n
if Nts[i] > i
return false
return true
主权参考
《贪心算法:贪心选择性与优化子结构 》
《Greedy Algorithms》
《《算法导论》| 第十六章 贪心算法》
《【洛谷日报#181】拟阵与最优化问题》