17. 1引言
首先通过对一个简单理论的讨论,初步理解贪婪思想。以下棋为例,每一步的决策都需要考虑对后续棋局的影响。而在网球(或排球)比赛中,选手的行为仅取决于当前的状况,选择当下最为正确的动作,而不关心后续的影响。这说明在某些情况下选择当下最佳行为的决策,可以得到一个最优解(贪婪),但并非所有情况都如此,贪婪策略适用于上述第二类问题。
17. 2贪婪策略的定义
贪婪算法将问题分为多个阶段。在每一个阶段,选取当前状态下的最优决策,而不考虑对后续决策的影响。这意味着算法在执行过程中会选取某些局部最优解。贪婪法假.设通过局部最优解可以获得全局最优解。
17.3贪婪算法的要素
最优贪婪算法需要满足两个基本性质:
- 贪婪选择性质。
- 最优子结构。
贪婪选择性质:全局最优解可以通过寻找局部最优解获得(贪婪),局部最优解的选择可能依赖于之前的决策,而不是后续的决策。通过迭代方式算法进行一一个个贪婪选择,将原问题简化为规模更小的问题。
最优子结构:如果原问题的最优解包含子问题的最优解,则认为该问题具有最优子结构。这意味着可以对子问题求解并构建规模更大问题的解。
17.4贪婪算法的适用范围
选择局部最优解不是对于所有问题都适用,所以贪婪算法并不总是能得到最优解。在17.8节和第19章将给出这样的例子。
17.5贪婪算法的优缺点
贪婪算法的优点是直观,易于理解,并易于编码实现。当前的决策不会对已经计算出的结果有任何影响,因此不需要再对已有的局部解进行检查。缺点是,对于许多问题,无法用贪婪算法求解。即在许多情况下,无法保证局部最优解能够产生全局最优解。
17.6 贪婪算法的应用
- 排序问题:选择排序、拓扑排序。
- 优先队列:堆排序。
- 赫夫曼编码压缩算法
- Prim和Kruskal算法。
- 加权图的最短路径问题(Dijkstra算法)。
- 硬币找零问题
- 分数背包问题。
- 并查集的按大小或高度合并问题(或排名)。
- 任务调度算法。
- 贪婪算法可用于求解复杂问题的近似算法。
17.7贪婪思想
本节通过-一个例子来更好地理解贪婪思想,更多细节参见17.6节的相关主题。赫夫曼编码算法
定义:给定来自字母表A的n个字符的集合(字符c∈A),并已知每个字符出现的频率freq(c),为每-一个字符c∈A找到一个二进制编码,使得> freq(c) | binarycode(c) |c∈A的值最小,其中| binarycode(c) |表示字符c的二进制编码的长度。以上公式表明所有字符编码长度之和(每个字符出现频率与编码的位数乘积之和)最小。
赫夫曼编码算法的基本思想是,对于出现频率较大的字符用更少的位来编码。利用可变长度编码,赫夫曼算法可以压缩数据存储所需的空间。计算机系统采用8位来表示每一个字符, 但并非所有的位都被使用。此外,某些字符的使用更为频繁。当读取一个文件时,系统通常每次读取8位来确定-一个字符。但是这种8位编码机制是低效的,因为相比而言,有些字符使用更为频繁。例如,字符‘e'往往比字符‘q'的使用频率高10倍。
因此,如果对于字符‘e'用7位编码,而‘q'用9位编码,这将减少整个消息的长度。平均而言,对于标准文件,使用赫夫曼编码在长度上能够减少10%~30%,具体的值取决于字符的频率。这种编码思想是,对于较少使用的字符或字符组采用较长的二进制编码。此外,赫夫曼编码满足任意两个字符的编码互不为前缀。
例子:假设扫描一个文件,得出以下字符频率:
首先,为每一个字符创建- - 棵二叉树,并将其出现频率存储在结点中(见下图)。
赫夫曼算法的流程如下:在列表中寻找根结点中存有最小频率值的两棵二叉树,创建一个不存储任何字符的结点,将这两棵二叉树作为新建结点的左右子树,并将其孩子结点的频率值之和存储到新建结点中。由此可以得出下图:
重复.上述过程直至仅剩下一棵树。