贪心法在许多最优化问题求解中得到了广泛应用,比如求最小生成树的Prim算法和Kruskals算法,求单源最短路径的Dijkstra算法,数据压缩的Huffman算法等.贪心法和动态规划的区别在于贪心法在每一步判断时不用考虑子问题的结果,只需要做出局部最优的选择.
贪心法设计技术的要素
- 贪心法使用于组合优化问题,该问题满足优化原则
- 求解过程是多步判断过程,最终的判断序列对应于问题的最优解
- 判断依据某种"短视的"贪心选择性质,性质的好坏决定了算法的成败
- 贪心法必须进行正确性证明
关于贪心法的正确性证明
1.通过对算法步数的归纳来证明其正确性
[问题1]活动选择问题
贪心策略:把活动按照截止时间从小到大排序,使得\(f_1\le f_2\le ... \le f_n\),然后从前向后挑选,只要与前面选的活动相容,就可以把这项活动选入A.
证明:算法执行到第\(k\)步,选择\(k\)项活动\(i_1,i_2,...,i_k\),那么存在最有解A包含\(i_1=1,i_2,...,i_k\).
将\(S\)中的活动按照截止时间递增顺序排列.
归纳基础: \(k=1\)时,算法选择了活动1.我们仅需要证明:存在一个最优解包含了活动1.设\(A=\{i_1,i_2,...,i_j\}\)是一个最优解,如果\(i_1\neq 1\),那么用1替换\(i_1\),得到\(A'\),即$$A'=(A-{i_1})\cup{1}$$那么\(A'\)和\(A\)的活动个数相等.且活动1比\(i_1\)结束得更早,因此和\(i_2,i_3,...,i_j\)等活动都相容.于是\(A'\)也是问题的一个最优解.
归纳步骤: 假设对于任意正整数\(k\),命题正确.令\(i_1=1,i_2,...,i_k\)是算法前\(k\)步顺序选择的活动,那么存在一个最优解$$A={i_1=1,i_2,...,i_k}\cup B$$如果令\(S'\)是\(S\)中剩下的与\(i_1,i_2,....,i_k\)相容的活动构成的集合,即
那么\(B\)是\(S'\)的一个最优解.如若不然,假如\(S'\)有解\(B'\),\(|B'|>|B|\),那么用\(B'\)替换\(B\)以后得到的解\(\{i_1=1,i_2,...,i_k\} \cup B'\)将比\(A\)的活动更多,与\(A\)是最优解矛盾.
根据对归纳基础的证明,算法第一步选择结束时间最早的活动总是导致一个最优解,故对于子问题\(S'\)存在一个最优解\(B^*=\{i_{k+1},...\}\).由于\(B^*\)与\(B\)都是\(S'\)的最优解,因此\(|B^*|=|B|\).于是
与\(A\)的活动数目一样多,也是一个最优解,而且恰好包含了算法前\(k+1\)步选择的活动.根据归纳法命题得证.
2.通过对问题规模的归纳来证明其正确性
[问题2]集装箱问题
贪心策略:轻者先装,直到再装任何集装箱将使轮船载重量超过C时停止.
证明:对于任何正整数\(k\),算法都对\(k\)个集装箱的实例得到最优解.
归纳基础: \(k=1\),只有一个集装箱,其重量\(w_1 \le C\),任何算法都只有一种装法,就是将这只集装箱装上船.
归纳步骤: 假设算法对于规模为\(k\)的输入都能得到最优解,考虑规模为\(k+1\)的输入\(N=\{1,2,...,k+1\}\),\(W=\{w_1,w_2,...w_{k+1}\}\)是集装箱重量,其中\(w_1\le w_2\le...\le w_{k+1}\).从\(N\)中拿掉最轻的集装箱,得到
根据归纳假设,对于\(N'\),\(W'\)和\(C'\),算法得到最优解\(I'\).令$$I=I'\cup{1}$$
那么\(I\)是\(N\)的最优解.这也恰好是算法对于\(N\),\(W\),\(C\)的解.
3.通过交换论证证明其正确性
[问题3]最优前缀码问题(Huffman算法)
贪心策略:找到两个最低频率的对象将其合并,得到的新对象的频率设置为原来两个对象的频率之和.
证明一:贪心选择性质
假设\(T\)是一棵最优二元前缀码对应的二叉树,且\(x\),\(y\)不是最深层的兄弟,那么存在最深层的2片树叶\(a\)和\(b\),使得\(d_T(x) \le d_T(a)\),\(f(x) \le f(a)\),\(d_T(y) \le d_T(b)\),\(f(y) \le f(b)\).把\(x\)与\(a\)交换,\(y\)与\(b\)交换,得到树\(T'\),那么两棵树的权值之差是:
于是\(T'\)也是一棵最优二元前缀码树.
证明二:最优子结构性质
\(\forall c \in C-\{x,y\}\),有
由于\(z\)是\(x\)与\(y\)的父亲,因此有
于是,将上式代入得
从而有