贪心算法
能够用贪心算法解决的问题,也展现两大性质:
- 最优子结构
- 贪心选择性质
最优子结构性质与dp类似,不再赘述。
贪心选择性质:当我们去做决定时间,要确保是目前最好的决定。通过选择局部最优解以期望得到全局最优解。
这就需要我们证明当要做选择时,最优解的其中一个是贪心选择,因此,选择贪心解才是安全的。除了贪心选择之外,其他子问题都是空的。
活动选择问题
输入n个活动的集合S a 1 , a 2 , … , a n a_1,a_2,\dots,a_n a1,a2,…,an,以及每个活动的开始时间和结束时间 s i , f i s_i,f_i si,fi。输出最大数量的可选活动的集合。
当两个活动的时间间隔不重叠时则这两个活动是相容的。
假设活动被按照结束时间进行排序,假设一个最优解包括了活动 a k a_k ak,这产生了两个子问题。
- 从 a 1 , a 2 , … , a k − 1 a_1,a_2,\dots,a_{k-1} a1,a2,…,ak−1中选择彼此兼容的活动,并且结束时间要比 a k a_k ak的开始时间早;
- 从 a k + 1 , … , a n a_{k+1},\dots,a_n ak+1,…,an中选择彼此兼容的活动,并且开始时间要比 a k a_k ak的结束时间晚。
我们同样可以利用”剪切-粘贴“法证明两个子问题的解同样是最优的。
令 s i j s_{ij} sij为S中活动的子集,这些活动开始的时间比 a i a_i ai结束的时间晚,并且结束的时间比 a j a_j aj开始的时间早。我们的目的就是在 s i j s_{ij} sij中选出最大数量的互相相容的活动。
让c[i,j]为 s i j s_{ij} sij中互相相容活动的最大数量。
dp解法
c
[
i
,
j
]
=
{
0
s
i
j
=
ϕ
m
a
x
{
c
[
i
,
k
]
+
c
[
k
,
j
]
+
1
}
s
i
j
!
=
ϕ
c[i,j] = \left\{ \begin{matrix} 0 \quad s_{ij} = \phi \\ max\{c[i,k] + c[k,j] + 1 \} \quad s_{ij} !=\phi \end{matrix} \right.
c[i,j]={0sij=ϕmax{c[i,k]+c[k,j]+1}sij!=ϕ
但是在活动选择问题中,具有贪心选择性质。
定理:如果S是按照结束时间排序的活动选择问题,那么S中存在最优集合A使得第一个结束的活动在A中。
证明:假设am是集合S中结束最早的活动,A为S的一个最大兼容子集。aj是A中结束最早的活动。如果am==aj则已经证明。如果不相等,则可以构造一个新的最大兼容子集A’,将A中的aj替换成am,已知A中活动相互兼容,而fj<fm。所以A‘中的活动也相互兼容。并且|A|=|A’|。因此我们成功构造了一个包含最早结束活动的最大兼容子集A’,得证。
因此,我们可以递归的使用贪心选择法解决此问题,并且除了贪心选择的子问题之外其他子问题都是空的。以此问题为例,因为s1<f1且f1是最早结束的活动,所以不会有活动结束的时间早于s1。因此,所有与a1兼容的活动都是在a1结束之后才开始的。
Recursive_Activity_Selector(s,f,i,j)
m <- i+1
while m<j and s[m] < f[i]
do m <- m+1 //s[m] >= f[i]
if m < j
then return {am} and Recursive_Activity_Selectors(s,f,m,j)
else return none
时间复杂度 Θ ( m ) \Theta(m) Θ(m)
迭代贪心算法:
GREEDY_ACTIVITY_SELECTOR(s,f)
n<- length(s)
A<- {a1}
i<-1
for m <- 2 to n
if s[m] >= f[i]
A <- A and {am}
i <- m
return A
贪心算法的典型步骤:
- 将最优化问题转换为这样的形式:对其做出一次选择之后,只剩下一个子问题待解决。
- 证明做出贪心选择之后,原问题总是存在最优解,即贪心选择是安全的。
- 证明做出选择之后,剩余的子问题满足性质:最优解与贪心组合即可得到问题的最优解
分数背包问题
证明分数背包问题具有贪心选择性质:
https://blog.csdn.net/u010339647/article/details/50588499
找零钱问题
给定零钱的币值,使得其组合等于n并且用的硬币数量最小。
贪心选择:永远选择币值最大的,并不是都正确。
dp:
令c[p]为找零p元所需要的硬币最少数量。x为最优解中使用的第一个硬币的值。
因此c[p] = 1 + c[p-x]。尝试所有可能的x找到最小的。
c
[
p
]
=
{
m
i
n
i
:
d
i
<
p
{
c
[
p
−
d
i
]
+
1
}
p
>
0
0
p
=
0
c[p] = \left\{ \begin{matrix} min_{i:d_i<p} \{c[p-d_i] + 1\} \quad p > 0 \\ 0 \quad p =0 \end{matrix} \right.
c[p]={mini:di<p{c[p−di]+1}p>00p=0
Dp_change(n)
c[0] = 0
for p = 2 to n
min = maxn
for i=1 to k
if (d[i] <= p)
if (c[p-d[i]+1<min)
min = c[p-d[i]] +1
coin = i
c[p] = min
s[p] = coin
时间复杂度 O ( n k ) O(nk) O(nk)
什么时候才可以进行贪心选择?
需要证明:
哈夫曼编码树
给文本编码需要满足的条件:尽可能使用少的编码长度,满足前缀码性质。
最佳前缀码
一种前缀编码的平均长度是所有符号的长度乘以他的频率之和。
A
B
L
(
C
)
=
∑
x
∈
S
f
x
∣
c
(
x
)
∣
ABL(C)= \sum_{x\in S} f_x |c(x)|
ABL(C)=x∈S∑fx∣c(x)∣
我们要做的就是使其最小。
因此采用哈夫曼编码树,它是一棵满二叉树,除了叶节点都有两个子节点。它使用的贪心思想是使得频率越高的节点越靠近根节点。
Huffman(S)
if |s|=2
设置一个根节点连接两个叶节点返回
else
选出S中两个频率最小的节点y and z
s' = s
将y和z从s'中删去
在s'中插入新的字母w,使得w的频率等于y和z之和
给T‘的叶节点w加上孩子节点y和z并赋值给T
返回T
T(n) = T(n-1) + O(n) (每次去两个点加一个新的点)
时间复杂度 O ( n 2 ) O(n^2) O(n2)
如何找到频率最小的两个节点: 单调队列,时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn)
证明哈夫曼编码的正确性(具有最优子结构和贪心选择性质)
参考算法导论:P248-249 (偷个懒,太长了)