算法导论——贪心算法

简介: 贪心算法(greedy algorithm)是指,在每一步都做出当时看起来最佳的选择,也就是局部最优的选择,期望这样的选择能够导向全局最优解。所以并不是所有的问题都能得到全局最优解。         典型的例子如分数背包问题:背包容量为50kg,有三个商品分别是重60元/10kg、100元/20kg、120元/30kg,商品可以分割,如何选取商品使背包价值最大。

贪心算法(greedy algorithm)是指,在每一步都做出当时看起来最佳的选择,也就是局部最优的选择,期望这样的选择能够导向全局最优解。所以并不是所有的问题都能得到全局最优解。

         典型的例子如分数背包问题:背包容量为50kg,有三个商品分别是重60元/10kg、100元/20kg、120元/30kg,商品可以分割,如何选取商品使背包价值最大。这里使用贪心算法,应当优先选择单位重量价值最大的商品,三种商品的单位重量价值分别为6、5、4,所以分别选取10kg、20kg和20kg,总价值为240元。但是,对于0-1背包问题——商品不能分割,此时的最佳方案是商品二20kg、商品三30kg总价值220元而不选择原本单位重量价值最高的商品一。由于背包的多余空间会改变商品的单位重量价值,所以不能以贪心算法来求解,应当转用动态规划。

         贪心算法最关键的一点在于要证明每一次做出的局部最优解与剩下子问题的最优解组合可以得到原问题的全局最优解

活动选择问题

下表为多个活动的开始时间si与结束时间fi,同一时间只能有一个活动进行,要找出一个不冲突的最大兼容活动集合。

i

1

2

3

4

5

6

7

8

9

10

11

si

1

3

0

5

3

5

6

8

8

2

12

fi

4

5

6

7

9

9

10

11

12

14

16

对于这个例子{a1,a4,a8,a11}和{a2,a4,a9,a11}都是最大兼容活动子集。

如果我们使用贪心算法,可以想到的策略应该是尽可能选择结束早的活动,给剩余活动留下更大的选择范围。

该想法成立由一个前提:任意非空子问题Sk,令am是Sk中结束时间最早的活动,则am在Sk的某个最大兼容活动子集中

证明Ak是Sk的一个最大兼容活动子集,且aj是Ak中结束时间最早的活动。若aj=am,则已经证明am在Sk的某个最大兼容活动子集中。若aj≠am,令集合Ak'=Ak-{aj}∪{am},即将Ak中的aj替换为am。Ak'中的活动都是不相交的,Ak中的活动也都不是不相交的,aj是Ak中结束时间最早的活动,而fm≤fi。由于丨Ak'丨=丨Ak丨,因此得出结论Ak'也是Sk的一个最大兼容活动子集且包含am

因此贪心算法是成立的。代码实现上,可以使用递归法:对于原问题选出最早结束的任务与剩余部分的最大兼容子集。后者可以继续递归下去。

1 resursiveActivitySelector(s,f,k,n){//初始调用参数为(s,f,0,n)
2     m=k+1;
3     for(m=k+1;m<=n && s[m]<f[k];m++);//寻找结束时间大于f[k]的最早结束的任务
4     if(m<=n)
5         return {a[m]} ∪ resursiveActivitySelector(s,f,k,n);
6     else
7         return NULL;
8 }

这属于一个典型的尾递归,也可以使用迭代算法,只需要不断寻找当前剩余活动中最早结束的那个加入到结果集合中。

greddyActivitySelector(s,f){
    n=s.length;
    A={a[1]};
    k=1;
    for(m=2;m<=n;m++){
        if(s[m]>=f[k]){
            A.add(a[m]);
            k=m;
        }
    }
    return A;
}

霍夫曼编码

         霍夫曼编码是通过将不同出现频率的字符编码为变长编码,出现频率高的则编码短,以节省空间。现在有一个文件中仅有6个不同字符出现,频率如下表所示

 

a

b

c

d

e

f

频率(千次)

45

13

12

16

9

5

定长编码

000

001

010

011

100

101

变长编码

0

101

100

111

1101

1100

         可以看到变长编码实际需要空间是(45*1+13*3*12*3+16*3+9*4+5*4)*1000=224,000位,而定长编码是300,000位。使用变长编码要求没有任何码字是其他码字的前缀,即所有编码可以唯一对应到一个结果。

         霍夫曼编码就是通过贪心算法来构造最优前缀码:

首先将所有字符按频率升序排列: f5 e9 c12 b13 d16 a45。从中取出最小两者相加f:5+e:9=14,重新按照大小插入回队列中: 12 13 14 16 45。然后再取出最小两者相加并插入:14 16 25 45。后续为25 30 45;45 55;100。对于每次取出的两个点,将他们作为二叉树的两个节点,他们的和为父节点,最终形成一颗二叉树,然后对每个分叉标上0和1,路径就是每个节点的变长编码。由于霍夫曼编码每次要获取两个最小值的特点,可以采用小堆排序来实现。

 

个人GitHub地址: https://github.com/GrayWind33
相关文章
|
9月前
|
算法
算法:贪心算法的原理和基本思路
算法:贪心算法的原理和基本思路
|
10天前
|
算法 C++ Python
数据结构与算法===贪心算法
数据结构与算法===贪心算法
|
6天前
|
算法 Java 定位技术
Java数据结构与算法:贪心算法之最短路径
Java数据结构与算法:贪心算法之最短路径
|
6天前
|
算法 Java
Java数据结构与算法:贪心算法之最小生成树
Java数据结构与算法:贪心算法之最小生成树
|
17天前
|
算法 机器人
【经典LeetCode算法题目专栏分类】【第5期】贪心算法:分发饼干、跳跃游戏、模拟行走机器人
【经典LeetCode算法题目专栏分类】【第5期】贪心算法:分发饼干、跳跃游戏、模拟行走机器人
|
21天前
|
存储 算法 数据挖掘
【贪心算法经典应用】哈夫曼编码原理与算法详解 python
【贪心算法经典应用】哈夫曼编码原理与算法详解 python
|
2月前
|
算法 调度 C语言
算法--贪心算法
贪心算法是每次选择当前最优解,期望达到全局最优,适用于有最优子结构的问题。它不回退,与动态规划不同。适用于分数背包问题,但0-1背包问题可能无法保证最优解。常见应用包括找零、最小生成树、单源最短路径和任务调度。贪心算法步骤包括建模、分问题、求局部最优解及整合。尽管简单,但需谨慎评估是否适用,例如0-1背包问题可能需用动态规划求解。
30 1
|
2月前
|
算法
算法人生(3):从“贪心算法”看“战胜拖延”(完美主义版)
本文探讨了拖延症的一个常见原因——完美主义,并从贪心算法的角度提供启示。贪心算法通过局部最优决策逼近全局最优解,不保证全局最优,但寻求满意解。完美主义者的拖延源于高标准、过度关注细节、压力和时间管理困难。为解决这个问题,建议接受不完美,设定合理目标,追求良好效果,以及培养时间管理技巧。通过实例说明,调整心态和策略,可以提高工作效率并克服拖延。
|
2月前
|
算法 程序员
贪心算法(被黑心公司强迫写的降薪算法)
贪心算法(被黑心公司强迫写的降薪算法)
|
2月前
|
算法 调度 C++
[数据结构与算法]贪心算法(原理+代码)
[数据结构与算法]贪心算法(原理+代码)