贪心法的思想
在求解一些最优化问题的时候,一般会分成多个步骤,每一步都有一个选择。贪心算法的思想在于,先不从整体考虑,每次都只做当前看来最优的思想,即局部最优解,期望通过一步步的局部最优解,最后构造出全局最优解。
贪心算法是很多问题的最优解,当然也有很多问题只是局部最优,或者近似最优解,在构造贪心算法的时候,要注意贪心选择是否能求出最后的最优解。
贪心算法的基本要素
最优子结构性质
一个问题的最优解包含其子问题的最优解,那么此问题具有最优子结构性质。前面的分治法,动态规划和此篇的贪心法,都具有最优子结构性质。
贪心选择性质
问题的整体最优解可以通过一些列局部最优的选择(贪心选择)来达到。因此贪心算法和分治法一样,都是自顶向下的思想。
贪心法的例子
0-1 背包问题
背包容量为 C ,共 n 个物品,物品 i 的(重量,价值)=( wi , vi ),求解背包装载的最大值。
贪心算法无法求出最优解。如果先按照单位价值最大排序,尽量装满背包,有时候得到的结果也近似最优解。
背包问题
和 0-1 背包不同的是,物品可以部分装载到背包中去。因此只要贪心地按照单位价值较大的物品顺序装满背包即可。
最优装载
轮船的载重量为 C ,物品 i 的重量为 Wi ,求解在体积不受限制的情况下,把最可能多的集装箱装上轮船。
可以用贪心的思想,把质量轻的物品优先装载。
哈夫曼编码
前缀码:任何一个字符的编码都不是其他字符编码的前缀(编码都是叶节点即可)
最优前缀码:使平均码长达到最小的前缀码
哈夫曼编码:自底向上构造表示最优前缀码的二叉树 T
贪心地合并两个具有最小频率的节点,并每次把合并后的节点加入到优先队列(最小堆)中去。
初始化优先队列O(n),最小堆的 removeMin 和 put 需要 O(logn)
单源最短路径
单源最短路径要解决的问题,是计算图中某个点到其他所有点的最短路径。形式化定义如下:假设带权有向图 G=(V,E),每条边的权都是非负实数,给定一个顶点,称为 源。计算从源到其他顶点的最短路长度,称单源最短路径问题。
如果边的权重都是非负的,一般用 Dijkstra 的贪心算法解决,效率较高。如果不是非负的,可以用 Bellman-Ford 算法,或者用其队列优化的改进版本,SPFS 算法。我有四篇博文讲了单源最短路径问题和这三种算法,不太详细,有空我会整理一下。我又开始挖坑了。。。
对了 Dijkstra 算法涉及优先队列和广度优先算法,还是值得一看的。
最小生成树
先定义这个问题,假设图 G 的子图 G’ 是包含所有顶点的树,那么 G’ 是图 G 的生成树,树上的总权重是该生成树的耗费,耗费最小的生成树称为 G 的最小生成树(MST,Minimum Spanning Tree)。
要计算最小生成树,可以利用贪心算法,一种是 Prim 算法,贪心地把点加入到一个集合,更新总权重;另一种是 Kruskal 算法,贪心地把圈中最小的边加入到 MST 中。
Prim 算法
思想是从一个点开始拓展,构成最小生成树(MST),每次找到剩下的点中与 MST 连接的权重最小的边 c[i][j],贪心地加入到 MST 中。
Kruskal 算法
先把边从大到小排序,贪心策略是权重小的边优先,这样把边一个个地加到 MST 中来;注意如果边在同一个连通分支中,就不能加入 MST 中,因为会构成闭环。
Kruskal 算法要用到并查集,计算时间复杂度为 O(eloge) ,比 Prim 算法的 O(n2) 要差一点,但是如果是稀疏图,边数比点数的平方少很多,那么 Kruskal 算法比 Prim 算法要好很多。
多机调度问题
多级调度问题是说,有 n 个作业,想用 m 台机器来完成,可以用“最长处理时间作业优先”的贪心策略
贪心算法的理论基础
拟阵,是个啥??【黑人问号.jpg】