《算法导论3rd第十六章》贪心算法

前言

适用于最优化问题的算法往往包含一系列步骤,每个步骤都面临多种选择。使用动态规划解决最优化问题,相当于计算出每咱选择,浪费大量效率。对于“特定”下的最优化问题,可以使用更简单更高效的贪心算法。贪心算法在每一步的选择中,都选择当时最佳的情况。即局部最优的选择->全局最优 思想。

活动选择问题

在这里插入图片描述

动态规划解法

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 akAvk最大化。设计一个多项式时间算法求解此问题。

令S[i,j]表示在活动a[i]结束之后开始,且在a[j]开始之前结束的那些活动。考虑S[i,j]之中的一个活动a[k],则S[i,j] = S[i,k] + a[k] + S[k,j],动态规划可解.
也可使用贪心:

  1. 对活动根据结束排序 a0,a1…an-1
  2. 根据开始时间取a0放入,计算出单位时间权值
  3. 现在取a1放入,如果a0和a1有重合,则比较 a0和a1单位时间权值
  4. 依此类推

贪心算法原理

一般情况下,我们可以按如下步骤设计贪心算法:

  1. 将最优化问题转换为这样的形式:对其做出一些选择后,只剩下一个子问题需要求解。
  2. 证明做出贪心选择后原问题总是存在最优解,即贪心选择总是安全的。
  3. 证明做出贪心选择后剩余的子问题满足性质:其最优解与贪心选择组合即可得到原问题的最优解,这样就得到了最优子结构。
贪心选择性质

一个全局最优解可以通过局部最优选择(贪心选择)达到, 不考虑子问题的结果;

  • 动态规划每一步都要做出选择,并且选择依赖于子问题的解(先求解出子问题的解,对解进行选择),所以动态规划要自底向上, 从小问题到大问题;
  • 在贪心算法中,我们总是做出当时看来最佳的选择,然后求解剩下的唯一的子问题。贪心算法进行选择时可能依赖之前做出的选择,但不依赖任何将来的选择或是子问题的解。因此贪心算法是自顶向下;
最优子结构

如果一个问题的最优解包含其子问题的最优解,则称此问题具有最优子结构性质。

  • 0-1 背包问题:一个小偷发现了n个商品,每件商品的价格和重量已知,小偷的背包只能容纳重量为W,小偷需要带走尽可能高价值的商品,每件商品只能整个带走而不能只带走一部分。(采用动态规划)
  • 分数背包问题:与0-1背包不同的是小偷可以只带走商品的部分而不必全部带走(采用贪心策略)

在这里插入图片描述

01背包和分数背包都有最优子结构性质, 但01背包不具有贪心选择性

练习

2-1证明:分数背包问题具有贪心选择性
  1. 证明命题:设𝑎𝑘是剩余物品中𝑣𝑖/𝑤𝑖 最大的物品,则对于这个子问题,𝑎𝑘必在最优解中。
  2. 假设存在𝑎𝑗(𝑤𝑗≤𝑤𝑘)使得∑𝑣𝑖更大
  3. 由于𝑣𝑘/𝑤𝑘≥𝑣𝑗/𝑤𝑘 所以𝑣𝑘≥𝑣𝑗
  4. 假设不成立
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加入集合)
哈夫曼算法的正确性

要证明哈夫曼算法的正确性,只要证明最优前缀码问题具有贪心选择性质和最优子结构性质

  1. 贪心选择性质:全局最优解可以通过局部最优选择(贪心选择)达到在这里插入图片描述

  2. 最优子结构性质:子问题的最优解和贪心选出来的解可以凑成原问题的最优解在这里插入图片描述

练习

贪心算法的理论基础

贪心算法在确定何时能够产生最优解的时候,其实用到一种叫做拟阵的组合结构。贪心算法不一定产生最优解,但是如果一个贪心问题可以转化为拟阵,这个贪心算法可以产生最优解;

拟阵

一个拟阵是满足下列条件的一个序对M= (S,e);

  1. S 是一个有穷非空集合;
  2. e是S的一个非空子集族,称为S的独立子集;具有遗传性质, 如果A是B的子集, 并且B是e的一个成员,则A是e的一个成员;
  3. 拟阵具有交换性:如果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)给出以下定义:

  1. 若A∈L,那么称 A 为独立集
  2. 对于独立集 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) xAw(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):

  1. S=E
  2. L={ x | x⊆E 且图 G’=(V,x) 中无环 }
  3. 这个 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了,

练习

(略)

一个调度问题

单个处理器对若干个单位时间任务进行最优调度, 其中每个任务都有一个截止期限和超时惩罚。
单个处理器上具有期限和惩罚的单位时间任务调度的输入如下,有三个数组:

  1. 包含有n个单位时间任务的集合S = {a1, a2, a3, …, an};
  2. n个整数值的期限d1, d2, …, dn, 即任务ai要求在di之前完成;
  3. n个非负的权值(惩罚代价)w1, w2, w3,…, wn;如果ai在di之前没有被调度,就会有惩罚wi;

目标:找出一个S的调度,使得总的惩罚最小;

令M=(S,L)

  1. S 即所给集合
  2. L={ x | x⊆S , 且存在对 x 的合理调度使总惩罚为 0 }
  3. 这个 M 满足子集系统的前两个条件

证明其具有遗传性与交换性即可。

  1. 遗传性:对任意A∈L, B⊆A , 显然B⊆S。因为 A∈L ,所以减少了部分任务的 B 也一定存在合理的调度使得总惩罚为0, 故M具有遗传性。
  2. 交换性:对任意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

在这里插入图片描述

  1. 根据贪心策略,安排76543的任务
  2. 任务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】拟阵与最优化问题

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值