算法导论——贪心算法

简介: 贪心算法(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
相关文章
|
1月前
|
算法 Python
在Python编程中,分治法、贪心算法和动态规划是三种重要的算法。分治法通过将大问题分解为小问题,递归解决后合并结果
在Python编程中,分治法、贪心算法和动态规划是三种重要的算法。分治法通过将大问题分解为小问题,递归解决后合并结果;贪心算法在每一步选择局部最优解,追求全局最优;动态规划通过保存子问题的解,避免重复计算,确保全局最优。这三种算法各具特色,适用于不同类型的问题,合理选择能显著提升编程效率。
50 2
|
6月前
|
算法 C++ Python
数据结构与算法===贪心算法
数据结构与算法===贪心算法
|
2月前
|
算法 Java C++
【贪心算法】算法训练 ALGO-1003 礼物(C/C++)
【贪心算法】算法训练 ALGO-1003 礼物(C/C++)
【贪心算法】算法训练 ALGO-1003 礼物(C/C++)
|
3月前
|
算法 前端开发
一文了解贪心算法和回溯算法在前端中的应用
该文章深入讲解了贪心算法与回溯算法的原理及其在前端开发中的具体应用,并通过分析LeetCode题目来展示这两种算法的解题思路与实现方法。
|
4月前
|
算法
【算法】贪心算法——柠檬水找零
【算法】贪心算法——柠檬水找零
|
4月前
|
算法
【算法】贪心算法简介
【算法】贪心算法简介
100 0
|
5月前
|
算法 Python
Python算法高手进阶指南:分治法、贪心算法、动态规划,掌握它们,算法难题迎刃而解!
【7月更文挑战第10天】探索Python算法的精华:分治法(如归并排序)、贪心策略(如找零钱问题)和动态规划(解复杂问题)。通过示例代码揭示它们如何优化问题解决,提升编程技能。掌握这些策略,攀登技术巅峰。
146 2
|
5月前
|
存储 算法 Python
Python算法界的秘密武器:分治法巧解难题,贪心算法快速决策,动态规划优化未来!
【7月更文挑战第9天】Python中的分治、贪心和动态规划是三大关键算法。分治法将大问题分解为小问题求解,如归并排序;贪心算法每步选局部最优解,不保证全局最优,如找零钱;动态规划存储子问题解求全局最优,如斐波那契数列。选择合适算法能提升编程效率。
75 1
|
5月前
|
存储 算法 大数据
Python算法高手的必修课:深入理解分治法、贪心算法、动态规划,让你的代码更智能!
【7月更文挑战第9天】在Python算法学习中,分治法(如归并排序)将大问题分解为小部分递归解决;贪心算法(如货币找零)在每步选择局部最优解尝试达到全局最优;动态规划(如斐波那契数列)通过存储子问题解避免重复计算,解决重叠子问题。掌握这三种方法能提升代码效率,解决复杂问题。
60 1
|
5月前
|
算法 索引 Python
逆袭算法界!Python分治法、贪心算法、动态规划深度剖析,带你走出算法迷宫!
【7月更文挑战第8天】分治法,如快速排序,将大问题分解并合并解;贪心算法,选择局部最优解,如活动选择;动态规划,利用最优子结构避免重复计算,如斐波那契数列。Python示例展示这些算法如何解决实际问题,助你精通算法,勇闯迷宫。
53 1