本文章题目和算法本身来自《算法设计与分析》(屈婉玲版),黑书(刘汝佳),理解为本人理解。如有补充或不同见解欢迎在下方留言区讨论。
目录
- 哈夫曼树
- 最小生成树:Prim
- 例:钓鱼
- 例:照亮的山景
- 例:过河问题
哈夫曼树
算法描述
为获得平均长度最短的编码,不断将字符集中使用频率最小的两个字符取出(不放回),合并成为一棵子树,将父节点作为一个字符放回字符集,使其频率为两个子节点的权值之和。这样构造的树是一棵提供最优编码的树,每一条从树根到树叶的路径都是一个字符的编码
核心问题
- 为什么每次要选取两个频率最小的字符(归纳基础)
- 证明牵扯到树的变化,而有关优化目标总是和树的高度有关,有失一般性,怎样处理?
试证
- 对于一个最优方案对应的二叉树T,倘若我将频率最低的两个子节点放到最底层,那么我就可以把它们合并,将二叉树规模缩小,运用归纳假设构造一个最优解
- 倘若我可以知道合并节点之后前后两棵树的关系,我就可以把这步贪心决策和这棵新树合并,证明它也有最优性。但首先需要先证明T合并之后的那棵树也是个最优的树
引理4.2
设 x , y x, y x,y是最优二叉编码树T 中频率最小的两个叶子节点,与最底层两个叶子节点 x x x,y$交换后不会改变树的最优性。显然有:
B ( T ) − B ( T ′ ) = [ f ( x ) − f ( a ) ] [ d T ( x ) − d T ( a ) ] + [ f ( y ) − f ( b ) ] [ d T ( y ) − d T ( b ) ] ≥ 0 B(T)-B(T^{'})=[f(x)-f(a)][d_T(x)-d_T(a)]+[f(y)-f(b)][d_T(y)-d_T(b)]\ge 0 B(T)−B(T′)=[f(x)−f(a)][dT(x)−dT(a)]+[f(y)−f(b)][dT(y)−dT(b)]≥0
引理4.3
x , y x, y x,y为二叉树T的两片叶子,设将其合并后新树为 T ′ T^{'} T′,则两棵树的关系为:
B ( T ) = B ( T ) + f ( x ) + f ( y ) B(T)=B(T)+f(x)+f(y) B(T)=B(T)+f(x)+f(y)
正式证明
- 由于不是按步骤归纳而是按规模归纳,所以第一步不是最重要的推理
- n = 2 n=2 n=2时结论显然
- 假设 n = k n=k n=k时成立。对于一棵有 n + 1 n+1 n+1个字符的二叉编码树,我们可以把两片频率最小的叶子 a , b a,b a,b放到最下面而不影响最优性
- 将其合并成节点 s s s获得树T’,则可以根据贪心算法用T‘的叶子节点的权值构造最优二叉编码树T*
- T’也是一棵最优二叉编码树,否则B(T’)>B(T*),两边同时加f(a),f(b),左边变成B(T),右面是将 a , b a,b a,b填到 s s s下面得到的新树,显然T的最优性失效,矛盾
- 所以把 a , b a,b a,b填到 s s s下面构成的新树的平均编码长度与最优编码相等,它也是个最优编码:使用贪心想法构成的最优编码
套路总结
对于归纳问题规模型贪心,有以下套路:
- 对于规模为 ( n + 1 ) (n+1) (n+1)的问题,证明贪心操作所涉及的元素不会影响到最优性(如挪节点)
- 合并,缩小问题规模为 T ′ T' T′,用归纳假设:规模为 n n n时可用贪心构造 T ∗ T^* T∗
- 证明 T ′ T' T′也是最优解,因为 T ′ T' T′和 T ∗ T^* T∗同规模,故展开后也同规模,故用贪心构造出的解对于 ( n + 1 ) (n+1) (n+1)规模也是最优解
讲的很抽象,多练习。
最小生成树:Prim
算法综述
- 将图G的顶点分为两个集合:处理过的 S S S和未处理过的 T − S T-S T−S
- 选择连接 S S S和 T − S T-S T−S的最短的边 e e e,并将 T − S T-S T−S侧的与 e e e相连的点加入到 S S S中
- 重复以上算法,直至 T − S T-S T−S为空
证明思路
- 与上一题不同,本题采用对贪心的步骤进行归纳,而非问题的规模
- 证明的关键在于第一步:使用第一步的证明作为后续子问题的归纳基础
- 通用思路:假设存在使第k步成立的最优方案,对于问题中剩下未处理的部分证明它必须用最优子问题的解法与前k步成立才能得到最终的最优方案
- 利用归纳基础:子问题的第一步可以用贪心算法,故构造了一个包含前k+1步贪心决策的最优决策。
证明实战
- 第一步必须是选择权值最小的边 e 1 = ( 1 , i ) e_1=(1, i) e1=(1,i),否则若使用的是 e 1 l , w ( e 1 l ) > w ( e 1 ) e_1^l, w(e_1^l)>w(e_1) e1l,w(e1l)>w(e1),在最终生成的树 T T T中若加入 e 1 e_1 e1则生成含 e 1 , e 1 l e_1, e_1^l e1,e1l的圈,破除 e 1 l e_1^l e1l后剩下的仍是一棵生成树,但总长下降,与最优性矛盾
- 假设存在最小生成树 T T T包括前k步选择的边,构成了已处理点集 S S S和未处理点集 V − S V-S V−S,根据最小生成树的定义, T T T的结构必须是 S S S中已经出现的树+将 S S S看成一个大点后剩下点的生成树合并的结果,运用第一步归纳假设即可证毕。
例:钓鱼
题目描述
在一条水平路边,有 n ( 2 ≤ n ≤ 25 ) n(2\le n \le 25) n(2≤n≤25)个钓鱼湖,从左到右编号为1、2、3、……、n.佳佳有 H ( 1 ≤ H ≤ 16 ) H(1 \le H \le 16) H(1≤H≤16)个小时的空余时间,他希望用这些事件钓到尽量多的鱼。他从湖1出发,向右走,有选择的在第 i + 1 i+1 i+1个湖边停留一定的时间钓鱼,最后在某一个湖边结束钓鱼。佳佳测出从第i个湖到第 i + 1 i+1 i+1个湖需要走 5 ∗ T i 5*T_i 5∗Ti分钟的路,还测出在在第 i i i个湖边停留,第一个5分钟可以钓到鱼 F i F_i Fi,以后再每钓5分钟鱼,鱼量减少 D i D_i Di,为了简化问题,佳佳假定没有其他人钓鱼,也不会有其他因素影响到他钓到期望数量的鱼。请设计算法给出佳佳能钓到最多的鱼的方案。
问题抽取
- 变量太多了,行走时间,停留时间
- 湖比较少,我们枚举最后停留的湖,把行走时间变成常量
- 问题中以5分钟作为基本单位,不妨把5去掉
- 行走时间为常量,那问题等价为佳佳可以瞬间移动
贪心策略
- 在第 i i i分钟瞬移到可以收获最多鱼的湖去抓鱼,更新这个湖的抓鱼量
- 第一步正确性显然,若存在最优解包含前 k k k步选择,则剩下的时间内也应是剩下时间内的最优选择,故可用第一步构造处使用贪心算法的解与前 k k k步成立
- 证明较显然故略去
- 最后不要忘记将 n n n中情况合并
复杂度
- 暴力枚举各时刻可获鱼数最大值: O ( H n 2 ) O(Hn^2) O(Hn2)
- 堆优化: O ( H n l o g n ) O(Hnlogn) O(Hnlogn)
总结
感觉问题为贪心解法时应注意控制可变量的个数,明确优化对象,方便思考
例:照亮的山景
题目描述
在一片山的上空,高度为 T T T处有 N N N个处于不同水平位置的灯泡。如果山的边界上某一点与灯i的连线不经过山上的其他点,我们称灯i可以照亮该点。开尽量少的灯,使得整个山景都被照亮
优化对象选择
如果向下看
- 所有的可行方案都照亮了所有的谷底
- 所有照亮所有谷底的方案都是可行方案(光路是可逆的)
- 能照亮一个谷底的灯必定是一个连续的区间(图先欠着)
那我们直接以谷底优化对象好了
算法综述
- 对每一个谷底,预处理能将其照亮的所有灯泡区间
- 选择最少的灯泡,使得每个区间中至少有一个灯泡被选中了(贪心)
贪心决策(做有智商的莽夫)
- 显然的决策:如果区间 I I I包含了区间 J J J,那么考虑区间 I I I的决策是无用的,因为必有一点在 J J J中, I I I自然满足
- 只需讨论所有部分重合和不重合的区间
- 莽夫:最左侧的区间是最危险的区间,那么就考虑它,选它最右面的点,以保证能照亮更多与它重合的区间,把这些被照亮的区间删去
证明
- 以步数做归纳,显然莽夫的思考过程就是我们的决策基础
- 若存在最优决策T包含前k步的决策,若再处理已选灯的区间是多余的,去掉他们,应对后面的区间进行最优决策,而由归纳基础得出存在以贪心决策开头的方案使得其为最优方案
- 都是一个套路。。。
- 时间复杂度:直接线性扫描,为 O ( n ) O(n) O(n)
例:潜泳比赛
题目描述
- 有一支由 N N N人组成的潜水队参与比赛,目标是全队游到对岸。
- 潜水需氧气筒,整队只有一个氧气筒。
- 氧气筒可以两人公用,但速度就是两人中较慢者的速度。
- 任意两人可以一同潜泳。
- 请设计最优方案,使得最后一名选手到达对岸的时间尽量早。
莽夫思路一
- 总需要一个人来把氧气筒送回来,而游得再快的人也会被游得慢的人拖累,不妨就让游得最快的人来回送氧气筒
莽夫思路二
- 如果让最慢的两个人一起过去,在对岸安排好送氧气筒回来的人怎么样?
- 把最快的两个人先送过去,让他们充当搬运工
请计算
- 1、 2、 5、 6 、8、 9
- 1、 2、 2、 2、 2、 2
如果合并这两种思路
- 这两种思路的共同点,就是把最慢的两个人送过去了,而对于不同的情况有不同的选择
- 若将所有人按照过河时间升序排序成 a 1 , a 2 , . . . , a N {a_1, a_2, ..., a_N} a1,a2,...,aN,那么易知如果 a 1 + a N − 1 − 2 a 2 < 0 a_1+a_{N-1}-2a_2<0 a1+aN−1−2a2<0时用算法1,反之用算法2,我们就能最快地把最慢的两个人送到对岸去
- 这样选择研究的对象看起来和谐了一些
证明归纳基础
- 归纳基础:将最慢的两个人先用最快的方式送过去,不会影响总时间
- 倘若存在最优策略不是这样:最慢的两个人要么是一起过,要么是拖累着别人过。
- 这两个人如果是一起过的,那就得有人回来送氧气筒。如果对面有人的话肯定不是他们两个中的一个回来送氧气筒。如果不是 a 1 或 a 2 a_1或a_2 a1或a2回来送,把 a 1 , a 2 a_1,a_2 a1,a2调到前面去会更优。那不如把他们的过河顺序都调到前面去
- 如果这两个人是分开过的,那么还是用 a 1 a_1 a1去送氧气筒最快,那么为什么不把顺序调到前面呢?
- 完整证明思路与前面基本一样,节省时间略去不提
- 说不太清楚,有更好的思路请到我的万年不更的博客下留言讨论
时间复杂度
- 只需线性扫描, O ( n ) O(n) O(n)结束
总结
!找好优化目标!
练:浇花问题
- 有一长方形花坛,在于其较长边平行的中轴线上安放了若干水龙头,每个水龙头能浇到草地的半径是已知的。
- 保证存在可以把所有草地都浇到的方案,请把其中一个选择水龙头最少的方案找出来