本文开始记录贪心算法的学习。
贪心算法的例子——活动选择问题
输入:S={1,2,...,n}为n项活动的集合,si、fi分别为活动i的开始和结束时间。
活动i 与j相容 ⟺si≥fj 或 sj≥fi
求:最大的两两相容的活动集A
输入实例:
i |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
si |
1 |
3 |
2 |
5 |
4 |
5 |
6 |
8 |
8 |
2 |
fi |
4 |
5 |
6 |
7 |
9 |
9 |
10 |
11 |
12 |
13 |
解:{1,4,8}
贪心算法
贪心算法(greedy algorithm)的挑选过程是多步判断,每步依据某种“短视”的策略进行选择,选择时注意满足条件相容性条件。
策略1:开始时间早的优先排序使s1≤s2≤...≤sn,从前向后挑选;
策略2:占用时间少的优先排序使得f1−s1≤f2−s2≤...≤fn−sn,从前向后挑选。
策略3:结束早的优先排序使得f1≤f2≤...≤fn,从前向后挑选。
策略1的反例
策略1:开始早的优先
反例:S={1,2,3}
s1=0,f1=20,s2=2,f2=5,s3=8,f3=15
策略2的反例
策略2:占时少的优先
反例:S={1,2,3}
s1=0,f1=8,s2=7,f2=9,s3=8,f3=15
策略3伪码
算法:Greedy Select
输入:活动集S,si,fi,i=1,2,...,n,f1≤...≤fn
输出:A⊆S,选中的活动子集
-
n←length[S]
-
A←{1}
-
j←1
-
for i←2 to n do
-
ifsi≥fj
-
thenA←A∪{i}
-
j←i
-
return A
完成事件 t=max{fk:k∈A}
运行实例
输入:S={1,2,...,10}
i |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
si |
1 |
3 |
0 |
5 |
3 |
5 |
6 |
8 |
8 |
2 |
fi |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
解:A={1,4,8},t=11
时间复杂度:O(nlogn)+O(n)=O(nlogn)
贪心算法的特点
设计要素:
-
贪心算法适用于组合优化问题;
-
求解过程是多步判断过程,最终的判断序列对应于问题的最优解;
-
依据某种“短视”的贪心选择性质判断,性质好坏决定算法的成败;
-
贪心法必须进行正确性证明;
-
证明贪心法不正确的技巧:举反例。
贪心算法的正确性证明
一个数学归纳法的例子
例:证明对于任何自然数n,1+2+...+n=n(n+1)/2
证 n=1,左边=1,右边=1×(1+1)/2=1
假设对任意自然数n等式成立,则
1+2+...+(n+1)=(1+2+...+n)+(n+1)=n(n+1)/2+(n+1)=(n+1)(n/2+1)=(n+1)(n+2)/2
第一数学归纳法
适合证明涉及自然数的命题P(n)
归纳基础:证明P(1)为真(或者P(0)为真)
归纳步骤:若对所有n有P(n)为真,证明P(n+1)为真
∀n,P(n)→P(n+1)P(1)n=1,P(1)⟹P(2)n=2,P(2)⟹P(3)...
第二数学归纳法
适合证明涉及自然数的命题P(n)
归纳基础:证明P(1)为真(或P(0)为真)
归纳步骤:若对所有小于n的k有P(k)真,证明P(n)为真
∀k(k<n∧P(k))→P(n)P(1)n=2,P(1)⟹P(2)n=3,P(1)∧P(2)⟹P(3)...
两种归纳法的区别
归纳基础一样:P(1)为真
归纳步骤不同:
证明逻辑
算法正确性归纳证明
证明步骤:
-
叙述一个有关自然数n的命题,该命题断定该贪心策略的执行最终将导致最优解。其中自然数n可以代表算法步数或问题规模。
-
证明命题对所有的自然数为真。
归纳基础(从最小实例规模开始)
归纳步骤(第一或第二数学归纳法)
活动选择算法的命题
命题:
算法Select执行到第k步,选择k项活动
i1=1,i2,...,ik
则存在最优解A包含活动i1=1,i2,...,ik。
根据上述命题:对于任何k,算法前k步的选择都将导致最优解,至多到第n步将得到问题实例的最优解。
归纳证明:归纳基础
令S={1,2,...,n}是活动集,且fi≤...≤fn。
归纳基础:k=1,证明存在最优解包含活动1
证: 任取最优解A,A中的活动按截止时间递增排列。如果A的第一个活动为j,j1,用1替换A的活动j得到解A′,即
A′=(A−{j})∪{1},
由于f1≤fj,A′也是最优解,且含有1。
归纳步骤
假设命题对k为真,证明对k+1也为真
证明:算法执行到第k步,选择了活动i1=1,i2,...,ik,根据归纳假设存在最优解A包含i1=1,i2,...,ik,A中剩下活动选自集合S′
S′={i∣i∈S,si≥fk}A={i1,i2,...,ik}∪B
B是S′的最优解。(若不然,S′的最优解为B∗,B∗的活动比B多,那么
B∗∪{1,i2,...,ik}
是S的最优解,且比A的活动多,与A的最优性矛盾。)
将S′看成子问题,根据归纳基础,
存在S′的最优解B′有S′中的第一个活动ik+1,且∣B′∣=∣B∣,于是
{i1,i2,...,ik}∪B′={i1,i2,...,ik,ik+1}∪(B′−{ik+1})
也是原问题的最优解。
最优装载问题
问题:n个集装箱1,2,...,n装上轮船,集装箱i的重量wi,轮船装载限制为C,无体积限制。问如何装使得上船的集装箱最多?不妨设每个箱子的重量wi≤C。
算法设计
正确性证明思路
-
命题:对装载问题任何规模为n的输入实例,算法得到最优解。
-
设集装箱从轻到重记为1,2,...,n。
归纳基础 证明对任何只含1个箱子的输入实例,贪心法得到最优解,显示正确。
-
归纳步骤 证明:假设对于任何n个箱子的输入实例贪心法都能得到最优解,那么对于n+1个箱子的输入实例也能得到最优解。
归纳步骤证明思路
N={1,2,...,n+1},w1≤w2≤...≤wn+1
⟹
去掉箱子1,令C′=C−w1,得到规模为n的输入N′={2,3,...,n+1}
⟹
关于输入N′和C′的最优解I′
⟹
在I′加入箱子1,得到I
⟹
证明I是关于输入N的最优解
正确性证明
假设对于n个集装箱的输入,贪心法都可以得到最优解,考虑输入
N={1,2,...,n+1},其中w1≤w2≤...≤wn+1
由归纳假设,对于
N′={2,3,...,n+1},C′=C−w1,
贪心法得到最优解I′。令
I=I′∪{1}
I(算法解)是关于N的最优解。
若不然,存在包含1的关于N的最优解I∗(如果I∗)
若不然,存在包含1的关于N的最优解I∗(如果I∗中没有1,用1替换中的第一个元素得到的解也是最优解),且∣I∗∣>∣I∣;那么I∗−{1}是N′和C′的解且
∣I∗−{1}∣>∣I−{1}∣=∣I′∣
与I′是关于N′和C′的最优解矛盾。
小结
最小延迟调度
客户集合A,∀i∈A,ti为服务时间,di为客户要求完成时间,ti,di为正整数,一个调度f:A→N,f(i)为客户i的开始时间。求最大延迟达到最小的调度,即求f使得
fmin{i∈Amax{f(i)+ti−di}}∀i,j∈A,i=j,f(i)+ti≤f(j)orf(j)+tj≤f(i)
实例:调度1
A={1,2,3,4,5},T={5,8,4,10,3},D=<10,12,15,11,20>。
调度1:顺序安排
f1(1)=0,f1(2)=5,f1(3)=13,f1(4)=17,f1(5)=27
各任务延迟:0,1,2,16,10;
最大延迟:16。
优化的调度2
A={1,2,3,4,5},T={5,8,4,10,3},D=<10,12,15,11,20>。
调度2:按截至时间从前到后安排:
f2(1)=0,f2(2)=5,f2(3)=13,f2(4)=17,f2(5)=27
各个任务延迟:0,11,12,4,10;
最大延迟:12。
贪心策略
贪心策略1:按照ti从小到大安排
贪心策略2:按照di−ti从小到大安排
贪心策略3:按照di从小到大安排
策略1对某些实例得不到最优解。
- 反例:t1=1,d1=100,t2=10,d2=10
策略2对某些实例得不到最优解。
- 反例:t1=1,d1=2,t2=10,d2=10
策略3
伪代码
算法 Schedule
输入:A,T,D
输出:f
-
排序A使得d1≤d2≤...≤dn
-
f(1)←0 // 从0时刻起
-
i←2
-
whilei≤ndo
-
f(i)←f(i−1)+ti−1
-
i←i+1
排序思想:按照完成时间从早到晚安排任务,没有空闲。
交换论证:正确性证明
证明思路:
贪心算法的解的性质:没有空闲时间,没有逆序
逆序(i,j):f(i)<f(j)且di>dj
引理
引理1:所有没有逆序、没有空闲时间的调度具有相同的最大延迟。
证:设f没有逆序,在f中具有相同的完成时间d的客户i1,i2,...,ik连续安排,其安排时刻为t0,完成这些任务的时刻是t,最大延迟为最后任务延迟t−d,与i1,i2,...,ik的排列次序无关。
t=t0+(ti1+ti2)+...+tik
证明要点
从一个没有空闲的最优解出发,逐步转变成没有逆序的解。根据引理1,这个解和算法解具有相同的最大延迟。
-
如果一个最优调度存在逆序,那么存在i<n使得(i,i+1)构成一个逆序,称为相邻的逆序。
-
交换相邻逆序i和j,得到的解仍旧最优。
-
每次交换后逆序数减1,至多经过n(n−1)/2次交换得到一个没有逆序的最优调度——等价于算法的解。
交换相邻逆序仍旧最优
设f1是一个任意最优解,存在相邻逆序(i,j)。交换i和j的顺序,得到解f2。那么f2的最大延迟不超过f1的最大延迟。
理由:
-
交换i、j与其他客户延迟时间无关。
-
交换后不增加j的延迟,但可能增加i的延迟。
-
i在f2的延迟小于j在f1的延迟。因此小于f1的最大延迟f。
i在f2的延迟不超过j在f1的延迟
delay(f2,i)=s+tj+ti−di
delay(f1,j)=s+ti+tj−dj
dj<di
delay(f2,i)<delay(f1,j)≤r
小结
贪心法的正确性证明方法:交换论证
得不到最优解的处理方法
找零钱问题
问题:设由n种零钱,重量分别为w1,w2,...,wn,价值分别为v1=1,v2,...,vn。需要付的总钱数是y。不妨设币值和钱数都为正整数。问:如何付钱使得所付钱的总重量最轻?
实例:v1=1,v2=5,v3=14,v4=18,wi=1,i=1,2,3,4.y=28
最优解:x3=2,x1=x2=x4=0,总重为2。
建模
令选用第i种硬币的数目是xi,i=1,2,3,...,n
min{i=1∑nwixi}i=1∑nvixi=y,xi∈N,i=1,2,...,n
动态规划算法
设Fk(y)表示用前k种零钱,总钱数为y的最小重量
Fk(y)=0≤xk≤⌊vky⌋min{Fk−1(y−vkxk)+wkxk}
F1(y)=w1⌊v1y⌋=w1y
贪心法
单位价值重量轻的货币优先,设
v1w1≥v2w2≥...≥vnwn
使用前k种零钱,总钱数为y,贪心法的总重量为Gk(y)。
Gk(y)=wk⌊vky⌋+Gk−1(ymodvk),k>1
G1(y)=w1⌊v1y⌋=w1y
n=1,2贪心法是最优解
n=1,只有一种零钱,F1(y)=G1(y)
n=2,x2越大,得到的解越好。
F2(y)=0≤x2≤⌊y/v2⌋min{F1(y−v2x2)+w2x2}[F1(y−v2(x2+δ))+w2(x2+δ)]−[F1(y−v2x2)+w2x2]=[w1(y−v2x2−v2δ)+w2x2+w2δ]−[w1(y−v2x2)+w2x2]=−w1v2δ+w2δ=δ(−w1v2+w2)≤0
判别条件
定理:对每个正整数k,假设对所有非负数y有Gk(y)=Fk(y),且存在p和δ满足
vk+1=pvk−δ,
其中0≤δ<vk,vk≤vk+1,p为正整数,
则下面的命题等价:
-
Gk+1(y)=Fk+1(y)对一切正整数y;
-
Gk+1(pvk)=Fk+1(pvk);
-
wk+1+Gk(δ)≤pwk。
几点说明
-
根据条件1和3的等价性,可以对k=3,4,...,n,依次利用条件3对贪心法是否得到最优解做出判别。
-
条件3验证1次需要O(k)时间,k=O(n),整个验证时间O(n2)。
-
条件2是条件1在y=pvk时的特殊情况。若条件1成立,显然有条件2成立。反之,若条件2不成立,则条件1不成立,钱数y=pvk恰好提供了一个贪心法不正确的反例。
验证实例
例1:v1=1,v2=5,v3=14,v4=18,wi=1,i=1,2,3,4。对一切y有
G1(y)=F1(y),G2(y)=F2(y).
验证G3(y)=F3(y)
v3=pv2−δ⟹p=3,δ=1.w3+G2(δ)=1+1=2pw2=3×1=3w3+G2(δ)≤pw2
贪心法对n=3的实例得到最优解
例2 v1=1,v2=5,v3=14,v4=18,wi=1,i=1,2,3,4.对一切y有
G1(y)=F1(y),G2(y)=F2(y),G3(y)=F3(y),
验证G4(y)=F4(y),
v4=pv3−δ⟹p=2,δ=10w4+G3(δ)=1+2=3pw3=2×1=2w4+G3(δ)>pw3
n=4,y=pv3=28,
最优解:x3=2,贪心法:x4=1,x2=2。
小结
-
贪心策略不一定得到最优解,在这种情况下有两种解决方法:
-
参数化分析:分析参数取什么值可以得到最优解。
-
估计贪心法得到的解在最坏情况下与最优解的误差。
-
一个参数化分析的例子:找零钱问题。