4 贪心算法
4.1
若在 0 − 1 0-1 0−1 背包问题中, 各物品依重量递增排列时, 其价值恰好依递减序排列. 对于这个特殊的 0 − 1 0-1 0−1 背包问题, 设计一个有效算法找出最优解, 并说明算法的正确性.
算法设计:由题目所给的信息可以知道这种特殊的背包问题可以通过贪心算法得到最优解。
正确性证明:采用反证法,各物品依重量递增、价值递减序排列时,如果存在其他最优解使得在总限重条件下背包的价值更高,可以分为以下三种情形:
①物品个数比贪心算法得到的解更少,根据题目条件和贪心算法的性质,此情况不可能存在。
②物品个数比贪心算法得到的解相等,由于贪心算法选取的顺序是价值递减的顺序,此情况不存在。
③物品个数比贪心算法得到的解更多,假设存在的最优解与贪心算法的解去掉二者交集后,最优解中还剩两个或两个以上的物品 x j , x k x_j,x_k xj,xk,其总价值大于贪心算法选取的一个物品 x i x_i xi,即 v j + v k > v i v_j+v_k>v_i vj+vk>vi,但是由于 w i < w j < w k w_i<w_j<w_k wi<wj<wk以及 v i > v j > v k v_i>v_j>v_k vi>vj>vk因此一定有 v j + v k < v i + v j v_j+v_k<v_i+v_j vj+vk<vi+vj,与贪心算法的机制违背,此情况不成立。
综上所述,贪心算法可以找到此问题的最优解。
数据输入:物品重量数组 W [ 1.. n ] W[1..n] W[1..n],物品价值数组 V [ 1.. n ] V[1..n] V[1..n],背包限重 w w w
结果输出:布尔类型数组 O [ 1.. n ] O[1..n] O[1..n],值为 T r u e True True代表相应的物品装入背包,反之 F a l s e False False为放弃该物品。
伪码描述:
- O [ 1.. n ] ← { F a l s e , F a l s e . . . F a l s e } O[1..n]\leftarrow\{False,False...False\} O[1..n]←{False,False...False}
- w e i g h t ← 0 weight\leftarrow0 weight←0
- i ← 1 i\leftarrow1 i←1
- w h i l e T r u e d o while\ \ True\ \ do while True do
- i f w e i g h t + W [ i ] ≤ w / / 验证能否放入背包 if \ \ weight+W[i]≤w\quad//验证能否放入背包 if weight+W[i]≤w//验证能否放入背包
- t h e n O [ i ] = T r u e ; i = i + 1 / / 放入背包并进入下一次循环 then\ \ O[i]=True;\ i=i+1\quad//放入背包并进入下一次循环 then O[i]=True; i=i+1//放入背包并进入下一次循环
- e l s e r e t u r n O [ 1.. n ] else\ \ return\ \ O[1..n] else return O[1..n]
复杂度分析:该算法使用贪心算法,时间复杂度随输入规模一次线性增长,因此易得算法的时间复杂度是
W
(
n
)
=
O
(
n
)
\begin{aligned} W(n)=O( n ) \end{aligned}
W(n)=O(n)
算法需要一个布尔类型数组
O
[
1..
n
]
O[1..n]
O[1..n]来记录背包放入情况,因此空间复杂度是
S
(
n
)
=
O
(
n
)
\begin{aligned} S(n)=O(n ) \end{aligned}
S(n)=O(n)
4.2
将最优装载问题的贪心算法推广到两艘船的情形, 贪心算法仍然能产生最优解么? 若能, 给出证明. 若不能, 请给出反例.
证明:
如果使用贪心算法求解,必须把第一艘尽可能的装满,才能使总的装载量更多,第一艘船装载的方式只可能有一种最优解,即装载的物品都是以从轻到重的顺序装载,但是这样装并不一定得到最优解,例如下面这个例子:
假设${w_1,w_2,w_3,w_4,w_5}={10,20,30,40,50},c_1=50,c_2=50 ,贪心算法得到的解为 ,贪心算法得到的解为 ,贪心算法得到的解为{10,20},{30} ,这很显然不是最优解,最优解为 ,这很显然不是最优解,最优解为 ,这很显然不是最优解,最优解为{10,40},{20,30}$。
综上所述,将最优装载问题的贪心算法推广到两艘船的情形,贪心算法并不能保证产生最优解,该问题可以采用回溯法或动态规划等其他方法解决得到最优解。
4.3
设 Γ = 1 , . . . , n Γ = {1, . . . , n} Γ=1,...,n 是 n n n 个字符的集合. 证明关于 Γ Γ Γ 的任何最优前缀码可以表示长度为 2 n − 1 + n ⌈ l o g n ⌉ 2n − 1 + n⌈log n⌉ 2n−1+n⌈logn⌉ 位的编码序列. (提示: 先考虑树结构的编码, 再考虑叶结点对应字符的编码)
证明:
最优前缀编码所对应的编码二叉树是一棵完全二叉树,有 n n n个叶节点和 n − 1 n-1 n−1个内节点,叶节点和内节点需要用 0 − 1 0-1 0−1区分,并通过对编码树前序遍历的结果来唯一确定编码树的结构,这个前序遍历结果有 2 n − 1 2n-1 2n−1位。
叶节点所携带的数据使用二进制编码表示,因此每个叶节点需要 ⌈ l o g n ⌉ ⌈log n⌉ ⌈logn⌉位编码,有 n n n个叶节点,因此共需要 n ⌈ l o g n ⌉ n⌈log n⌉ n⌈logn⌉位编码来表示叶节点携带的数据。
综上,关于 Γ Γ Γ 的任何最优前缀码可以表示长度为 2 n − 1 + n ⌈ l o g n ⌉ 2n − 1 + n⌈log n⌉ 2n−1+n⌈logn⌉ 位的编码序列。
4.4
给定 x x x 轴上 n n n 个闭区间. 去掉尽可能少的闭区间, 使得剩下的闭区间都不相交. 设计一个有效算法找出最优解, 并说明算法的正确性.
算法设计:首先对所有区间按照右端点升序的顺序进行排序,然后在排序后的结果中统计不相交的区间个数,最后将总区间个数减去不相交的区间个数计算出需删掉的区间个数。
正确性证明:由于首先对所有区间按照右端点升序的顺序进行了排序,需要尽可能保留更多的区间,因此这个问题就可以转化为 4.1 4.1 4.1中的特殊 0 − 1 0-1 0−1背包问题,具体证明不再赘述,详见 4.1 4.1 4.1。
数据输入:闭区间个数 n n n, n n n个闭区间组成的数组 x [ n ] [ 2 ] x[n][2] x[n][2]
结果输出:需要去掉的尽可能少的闭区间数 c u t N u m b e r cutNumber cutNumber
伪码描述:
- x ← Sort ( x ) / / 首先对所有区间按照右端点升序的顺序进行排序 x \leftarrow \operatorname{Sort}(x) \quad / /首先对所有区间按照右端点升序的顺序进行排序 x←Sort(x)//首先对所有区间按照右端点升序的顺序进行排序
- c u t N u m b e r ← 0 / / 需要去掉的尽可能少的闭区间数 cutNumber\leftarrow0\quad//需要去掉的尽可能少的闭区间数 cutNumber←0//需要去掉的尽可能少的闭区间数
- c u r v e ← 1 curve\leftarrow1 curve←1
- f o r i ← 2 t o n d o / / 遍历所有区间 for \ \ i \leftarrow 2 \ \ to \ \ n \ \ do\quad//遍历所有区间 for i←2 to n do//遍历所有区间
- i f x [ i ] [ 1 ] ≥ x [ c u r v e ] [ 2 ] / / 验证区间是否重合 if \ \ x[i][1]≥x[curve][2]\quad//验证区间是否重合 if x[i][1]≥x[curve][2]//验证区间是否重合
- t h e n c u r v e = i / / 没有重合 then\ \ curve=i\quad//没有重合 then curve=i//没有重合
- e l s e c u t N u m b e r = c u t N u m b e r + 1 / / 存在重合,需要去掉 else\ \ cutNumber=cutNumber+1\quad//存在重合,需要去掉 else cutNumber=cutNumber+1//存在重合,需要去掉
- r e t u r n c u t N u m b e r return\ \ cutNumber return cutNumber
复杂度分析:该算法使用贪心算法,时间复杂度随输入规模一次线性增长,因此易得算法的时间复杂度是
W
(
n
)
=
O
(
n
)
\begin{aligned} W(n)=O( n ) \end{aligned}
W(n)=O(n)
默认排序算法都在原数组上完成,因此算法只需要常数规模的存储空间,空间复杂度是
S
(
n
)
=
O
(
1
)
\begin{aligned} S(n)=O(1 ) \end{aligned}
S(n)=O(1)