跟我一起学算法——贪心算法
@
贪心算法(Greedy Algorithms)
思想
每次选择总是选出当前最优的解
特点 :只计算部分子问题,而动态规划需要计算全部子问题,但同样依赖最优子结构性质。
步骤 :先排个序,再按每次最优循环选择。
条件
- 最优子结构性质
问题的整体最优解中包含子问题的最优解 - 贪心选择性质:局部最优=>全局最优
本次贪心选择与剩余子问题的最优解可以构成原问题的最优解。
贪心算法正确性的证明()
包括两部分:贪心选择性质的证明和最优子结构证明
贪心选择的证明,方法一:
- 给出贪心算法A的描述
- 反证:先给出一个贪心算的最优解A,然后假设非贪心选择的B是最优解,B可由A序列中调换元素
得到,证明B的解值比A差。
贪心选择的证明,方法二——剪枝法:
- 贪心算法A的描述
- 假设O是一个最优算法
- 找出O和A中的一个不同
- Exchange这个不同的东西,然后讨论得到的算法O'不比O差
- 若这种不同共有n个,然后我exchange n次就可以消除所有不同,得到A,且算法的质量不比O差。
这说明A as good as O,所以A是最优的。如huffman树贪心选择性质证明
最优子结构证明:
- 给出最优解A和子问题最优解B
- 假设B不是最优,C才是最优
- 证明将C替换B后的得到的A'优于A,矛盾。
应用:活动选择问题
也可以看做时间片段集合问题。给一些时间片段集合T={(a1,b1)(a2,b2)...(an,bn)},
找出一个元素个数最多的子集S,子集中的每个元素的时间片段没有交叉。O(n)
先把活动按结束时间排序,然后每次找上一次活动结束后可以开始的第一个活动。
贪心选择的证明
https://blog.csdn.net/rookie_ly/article/details/79039295
应用:Huffman编码
算法
使用最优前缀码,每次挑选二个频度值最小的字符,作为孩子节点,父结点的频度值为孩子的频度和。
haffman树的叶节点是满的。
def Huffman(charList):
while len(charList)!=1:
d=Node()
x=min(charList)
d.left=x
charList.del(x)
y=min(charList)
d.right=y
charList.del(y)
d.val=d.left.val+d.right.val
charList.append(d)
return charList[0] #根结点
时间复杂度O(nlgn)
证明贪心选择性质
令C为一字符集,C中每个字符c∈C的频度为f(c),x,y∈C是二个具有最小频度值的字符,则必定
存在C的一种最优前缀码,使x,y的码长相同且仅最后一位不同。
- 最优前缀码树是一棵叶结点满二叉树
- 令T是最优前缀码树,证明x,y码长相同且仅最后一位不同
证明最优子结构性质
若在C中去除字符x,y,加上频度为f[z]=f[x]+f[y]的新字符z,则构成一新的字符集
C’=C-{x,y}∪{z}。令T’是C’的一棵最优前缀码树,那么当用孩子结点为x,y的内部结点替换C’
中的叶结点z后得到的新树T是一棵最优前缀码树。
反证法
用huffman编码斐波那契数
a:1 11111110
b:1 1111110
c:2 111110
d:3 11110
e:5 1110
f:8 110
g:13 10
h:21 0
可推广到n。
应用:最小生成树( Kruskal Algorithm for Minimum Spanning Tree)
对于边集合E,先按每个边的cost排序,从小到大,然后按如下方法构造一个MST,每次选cost最小
的边加入MST,如果这条边使MST出现环路,则舍弃,然后重复上面的操作。
贪心算法正确性证明
- 图为G(V,E),假设上述算法A的MST为T2,有 最优算法O的MST为T1,T2 != T1.即至少存在一条边e,
它在T1,不在T2。e把T1分为两个集合Ta和Tb,且Ta中的点+Tb中的点=V。则在T2中一定存在一条边w(w!=e),
w的一个v在Ta中,另一个在Tb中。由A的定义知,选了w而没选e,说明 cost(w)<cost(e)。 - 构造一棵新的树T3= E(T1) - e + w,显然T3是一个生成树,且cost(T3)<=cost(T1),则T3是MST。
- T3比T1更接近T2,若T1和T2有n条不同的边,则按上述方法经过n次变化,可以得到T2,同时cost<=T1,
则T2是MST。
删数问题
贪心策略
从高到低位搜索,若各位数字递增,则删除最后一个数字,窦泽删除第一个递减区间的首字符(换句
话说:删除递增区间的最后一个数字),然后回到首位继续搜索。
贪心选择正确性证明
T1 = a_1*10^(n-1) + a_2*10^(n-2)+ ...+a_(n-1)*10+a_n
- 若各位数单调递增,则删除最后一个数:
T2 = a_1*10^(n-2) + a_2*10^(n-3)+ ...+a_(n-2)*10+a_(n-1)
若删除的另一个数a_k,则
T2'=a_1*10(n-2)+a_2*10(n-3)+a_(k-1)*10(n-k)+a_(k+1)*10(n-k-1)+...+
a_(n-1)*10+a_n
显然,T2-T2'=(a_k-a_(k+1)*10^(n-k-1) + (a_(k+1)-a_(k+2))*10^(n-q-2)+...+
(a_(n-2)-a_(n-1))*10+(a_(n-1)-a_n) <= 0
所以贪心选择是正确的。 - 若存在递减,则删除递减序列第一个数:
其他应用
胚(matroids)
定义
胚是满足以下条件的有序对M=(S,I):
- S有穷非空集
- I的遗传性:I是S的非空独立子集族,使得若B∈I,且A是B的子集,则A∈I。即B独立,B的子集亦独立。
- M的交换性:若A∈I,B∈I且|A|<|B|,则存在一个元素x∈B-A,使得A∪{x} ∈I。
例如,无向图G=(V,E),SG=E为G中的边集,IG为SG的无回路边集族,MG=(SG,IG)是一个胚。
最大独立子集
给定一个胚M=(S,I),对于I中一个独立子集A∈I,若S中有一个元素x不属于A,使得将x加入A后仍保
持A的独立性,即A∪{x}∈I,则称x为A的一个可扩张元素(extension)。
当胚中的一个独立子集A没有可扩张元素时,称A是一个最大独立子集。
胚中所有最大独立子集大小相等。
加权胚和最优子集
权值最大的独立子集称为最优子集。
通用贪心算法:按权值递减排序,循环取权值最大的+独立性检测。O(nlgn)