文章目录
- 一,什么是贪婪算法
- 二,最短路径
- 三,使用贪婪解题策略的演算法
- 3.1 活动选择问题
- 3.2 贪婪选择(Greedy options)
- 3.3 将动态规划解转化为贪婪解
- 四,高效的贪婪算法
- 4.1 贪婪算法的概述及特点
- 4.2 设计贪婪算法
- 4.3 递归贪婪算法
- 4.4 迭代贪婪算法
- 五,贪婪策略的要素
- 5.1 贪婪与动态规划
- 5.1.1 背包问题
- 5.1.2 背包演算法
- 5.1.3 0-1背包问题
- 5.2 Huffman编码
- 5.2.1 Huffman编码范例
- 5.2.2 树对应于编码方案
- 5.2.3 构造哈夫曼码
- 5.2.4 Huffman演算法的步骤:
- 六,使用贪婪解题策略的演算法
一,什么是贪婪算法
解决最简化问题的演算法,其解题过程可看成是由一连串的决策步骤所组成,而每一步骤都有一组选择要选定。
贪婪演算法的特性是 每一次选择都采取
- 一个贪婪算法在每一决策步骤总是选定当下看来最好的选择
- 贪婪算法并不保证总是得到最佳解,但在有些问题可以得到最佳解
二,最短路径
找出下面多级图的最短路径
则使用贪婪算法,从S到T的最短路径为:1+2+5 = 8
我们再来看一个比较复杂的例子:
我们是用贪婪算法得到的最短路径为:
而真实的最短路径为:
可以看出又是使用贪婪算法得出的最终路径并非是真实的最短路径。
三,使用贪婪解题策略的演算法
- 活动选择(activity-selection)演算法
- 背包(Knapsack)演算法
- Huffman编码演算法
- Kruskal最小扩张树演算法
- Prim最小扩张树演算法
- Dijkstra最短路径演算法
3.1 活动选择问题
- 假设有
n 个活动提出申请要使用一个场地,而这场地在同一时间点时最多只能让一个活动使用。 - 从这
n 个活动选一组数量最多,且可以在这场地举办的活动集。 - 假设活动 ai 其提出申请使用场地的时段为半开半闭的区间 [ si, fi )
一个活动选择问题
假设我们有一集合 S = { a1, a2, …,an },其中有 n 个行销活动
- 对于每一个活动 ai,其开始时间为 si 和结束时间为 fi(当 0 ≤ ai < fi < ∞)
- 活动 ai在半开放时间间隔内发生 [ si, fi )
如果间隔▼显示 = c[i, k] + c[k, j] + 1
活动选择问题
设
- ai ∈! S → S 是 P(A{ai}) 的最优解
- ai ∈ S → S {ai} 是 P(A\N[i]) 的最优解,但不一定是 P(A{ai}) 的最优解
↑ 每一条横线段代表一个活动,其横向位置代表了发生的时段,若纵向有交叉的部分这说明两时间冲突。
那么,我们可以做到更好吗?
动态规划(Dynamic programming): O(n2)
贪婪的解决方案 - 有没有办法反复做出局部决定?
- 关键:我们还是希望最终得到最优解
3.2 贪婪选择(Greedy options)
1,选择最早开始的活动,i.e. min{s1, s2, s3, …, sn}?
↓ 可以看出,选择的不是最优解
,2,选择时间最短的活动,i.e. min{ f1 - s1, f2 - s2, f3 - s3, …, fn - sn }?
↓ 可以看出,选择的也不是最优解
3,选择冲突次数最少的活动(与其共同发生的活动称为冲突活动)
选择结果如下 ↓
4,选择最早结束的活动,i.e. argmin{ f1, f2, f3, …, fn }?
蓝色是我们选择出的活动,而红色是与蓝色冲突的活动
然后把红色活动删掉,并且选择出第二早结束的活动(再继续上边的操作:删除冲突活动)
最终,经过选择,会留下以下4个活动
3.3 将动态规划解转化为贪婪解
考虑任何非空子问题Sij,并让am成为Sij中完成时间最早的活动:fm = min { fk : ak ∈ Sij}
则有如下性质:
- 活动am用于Sij中相互兼容活动的一些最大子集。
- 子问题Sim为空,因此选择am会使子问题Smj成为唯一可能非空的子问题。
四,高效的贪婪算法
4.1 贪婪算法的概述及特点
一旦你确定了一个合理的贪婪算法(reasonable greedy heuristic):
- 证明它总是给出正确的答案
- 可以开发有效的解决方案
贪婪方法概述
- 贪婪法则挑选一项活动来安排
- 将该活动添加到答案中
- 删除该活动和所有冲突的活动,称为 A ’
- 重复操作A ’,直到A ’为空集合
贪婪算法的特点
- 贪婪选择特性:通过局部最优(optimal)选择可以得到全局最优解。
- 最优子结构:在子问题的最优解范围内的问题的最优解。
4.2 设计贪婪算法
- as one in which we make a choice
做出选择 - and with one subproblem to solve
并且解决一个子问题
- 如果我们将子问题的最优解与我们所做的贪婪选择相结合,我们就得到了原问题的最优解。
一个活动选择的例子:
问题:选择相互兼容的活动的最大子集。
我们假设活动按结束时间的单调递增顺序排序:f1 ≤ f2 ≤ f3 ≤ … ≤ fn-1 ≤ fn
为了方便观察可以换个简易图,如下:
很容易看出,相互兼容(不冲突)活动的子空间有:
- {a1; a4; a8; a11}
- {a2; a4; a9; a11}
- {a3; a9; a11}
4.3 递归贪婪算法
1 2 3 4 5 6 7 8 | '递归活动选择器(s, f, k, n)' RECURSIVE-ACTIVITY-SELECTOR(s, f, k, n) 1 m ← k +1 2 while m ≤ n and s[m] < f [k] # Find the first activity in Sij 3 do m ← m + 1 4 if m ≤ n 5 then return {am} ∪ RAS(s, f, m, n) 6 else return 0 |
递归活动选择器对11个活动的操作
4.4 迭代贪婪算法
1 2 3 4 5 6 7 8 9 10 | '贪婪-活动-选择器' GREEDY-ACTIVITY-SELECTOR(s, f) 1 n ← s.length 2 A ← {a1} 3 k ← 1 4 for m ← 2 to n 5 do if s[m] ≥ f[k] 6 then A ← A ∪ {am} 7 k ← m 8 return A |
五,贪婪策略的要素
- 确定问题的最优子结构;
- 开发递归解决方案;
- 证明如果我们做出贪婪的选择,那么只剩下一个子问题;
- 证明贪婪的选择总是安全的;(步骤3和4可以按任意顺序进行。)
- 开发实现贪婪策略的递归算法;
- 将递归算法转换为迭代算法。
另一方面,我们可以在考虑贪婪选择的情况下,设计出最优的子结构。
5.1 贪婪与动态规划
- 背包问题
- 0-1背包问题
5.1.1 背包问题
定义
- 给定一个最大载重容量(capacity)为 W 的背包,以及n个可以放入背包的物品,其中第i个物品重为wi > 0,价格为pi > 0。
- 目标:找出 x1,…,xn以最大化 Σ1≤i≤n pixi
- 限制条件为: Σ1≤i≤n wixi ≤ W,其中 0≤xi≤1,1≤i≤n
对定义进行整理得出
- n个物件,背包容量为 W
– 每个物件,重量 wi > 0
– 每个物件,利润 pi > 0 - 最大限度 Σ1≤i≤n pixi
- 限制条件 Σ1≤i≤n wixi ≤ W(0≤xi≤1,1≤i≤n)
5.1.2 背包演算法
Algorithm 背包演算法
Input : 背包的最大容量W,以及可以放入背包的n个物品的非负重量wi 与 价格piOutput : 介于0与1之间的x1,…,xn分別代表第1个,…,第n个物品放入背包中的零碎部份。可以最大化 Σ1≤i≤n pixi,并且满足 Σ1≤i≤n wixi ≤ W(0≤xi≤1,1≤i≤n)
1,将pi/wi由大至小排序。
2,根据此排序來將物品依序尽可能地放入背包中,直至背包容量W用完为止。
背包演算法时间复杂度
- 依 pi/wi由大至小排序: O(n log n)
- 將物品依序放入背包: O(n)
总时间复杂度: O(n log n)
5.1.3 0-1背包问题
定义
- 给定一个最载容量(capacity)为W的背包,以及n个可以放入背包的物品,其中第i个物品的重量為 wi > 0,价格为 pi > 0。
- 目标:目标:找出 x1,…,xn以最大化 Σ1≤i≤n pixi
- 限制条件为: Σ1≤i≤n wixi ≤ W,其中 xi=0或1,1≤i≤n
背包问题 与 0/1背包问题的不同点在于:
- 在0/1背包问题中,xi只能是0或1
- 而在背包问题中,0 ≤ xi ≤ 1
贪婪策略不适用于0-1背包
背包中是否包含物品
- 我们必须将解决方案与包含物品的子问题和排除该物体的子问题的解决方案进行比较。
以这种方式表述的问题会产生许多重叠的子问题——动态规划的一个特征
5.2 Huffman编码
字元编码(character coding)可以分为:
- 固定长度编码: 如ACSII、Unicode
- 可变长度编码: Huffman code
Huffman编码以字首码(prefix code)方式达到字元编码最佳资料压缩(optimal data compression)
- 字首码 (prefix code): 任何字元编码一定不是其他字元编码的字首(prefix)。
- 可以使用二元树来呈现,达到简单编码(encoding)与解码(decoding)的功能。
5.2.1 Huffman编码范例
假设给定一个仅用到a, b, c, d, e五个字元的文件,现在想针对五个字元进行编码,以下是可能的固定长度编码与可变长度的Huffman字首码。
字首码(Prefix code) 让出现频率较高字元的编码较短, 以达到使用最少位元就可以将所有资料儲存的目标。
100000个字符的数据文件只包含字符a–f
- 为每个字符分配一个3位码字,文件编码为300,000位
- 使用所示的可变长度代码,我们可以将文件编码为224,000位(节省大约25%)
5.2.2 树对应于编码方案
5.2.3 构造哈夫曼码
1 2 3 4 5 6 7 8 9 10 | HUFFMAN( C ) 1 n ← |C| 2 Q ← C 3 for i ← 1 to n – 1 4 do allocate a new node z 5 left[z] ← x ← EXTRACT-MIN(Q) 6 right[z] ← y ← EXTRACT-MIN(Q) 7 f[z] ← f[x] + f[y] 8 INSERT(Q, z) 9 return EXTRACT-MIN(Q) |
Huffman编码演算法时间复杂度
- 行2: O(n)建立优先佇列Q
- 行3-8: for回圈一共執行n-1次,而且回圈中的优先佇列操作均为O(log n)复杂度,因此整個回圈具有O(n log n)的复杂度
总时间复杂度: O(n log n)
5.2.4 Huffman演算法的步骤:
六,使用贪婪解题策略的演算法
- 活动选择(activity-selection)演算法
- 背包(Knapsack)演算法
- Huffman编码演算法
- Kruskal最小扩张树演算法
- Prim最小扩张树演算法
- Dijkstra最短路径演算法