算法导论 — 4.1 最大子数组问题

笔记

本节给出了分治法的一个例子。给定一个数组 A [ 1.. n ] A[1..n] A[1..n],找出一个元素和为最大的连续子数组 A [ i . . j ] A[i..j] A[i..j],其中 1 ≤ i ≤ j ≤ n 1 ≤ i ≤ j ≤ n 1ijn,称这样的子数组为最大子数组。例如,下图所示数组中,第 8 8 8个元素到第 11 11 11个元素之间的子数组为最大子数组。
  在这里插入图片描述
  求解最大子数组问题,最简单的方法是暴力检查所有的子数组,从中找出和为最大的子数组。对于一个有 n n n个元素的数组,一共有 C n 2 + C n 1 = n ( n − 1 ) / 2 + n = Θ ( n 2 ) C_n^2+C_n^1=n(n-1)/2+n=Θ(n^2) Cn2+Cn1=n(n1)/2+n=Θ(n2)个子数组。参考练习4.1-2可知,计算每个子数组的和只需要 O ( 1 ) O(1) O(1)时间。因此暴力求解法的所花费的时间为 Θ ( n 2 ) Θ(n^2) Θ(n2)
  除了暴力求解法,最大子数组问题还可以使用分治法求解,并且分治法具有更优的时间复杂度。假定要寻找子数组 A [ l o w . . h i g h ] A[low..high] A[low..high]的最大子数组,我们从中央位置 m i d = ⌊ ( l o w + h i g h ) / 2 ⌋ mid=⌊(low+high)/2⌋ mid=(low+high)/2 A [ l o w . . h i g h ] A[low..high] A[low..high]划分为两个子数组, A [ l o w . . m i d ] A[low..mid] A[low..mid] A [ m i d + 1.. h i g h ] A[mid+1..high] A[mid+1..high]。于是, A [ l o w . . h i g h ] A[low..high] A[low..high]的任何一个子数组 A [ i . . j ] A[i..j] A[i..j]必然是以下三种情况之一:
  • 完全位于子数组 A [ l o w . . m i d ] A[low..mid] A[low..mid]中,即 l o w ≤ i ≤ j ≤ m i d low ≤ i ≤ j ≤ mid lowijmid
  • 完全位于子数组 A [ m i d + 1.. h i g h ] A[mid+1..high] A[mid+1..high]中,即 m i d < i ≤ j ≤ h i g h mid < i ≤ j ≤ high mid<ijhigh
  • 跨越了中央位置 m i d mid mid,即 l o w ≤ i ≤ m i d < j ≤ h i g h low ≤ i ≤ mid < j ≤ high lowimid<jhigh
  根据以上分析,可以递归求解最大子数组问题。对于一个子数组 A [ l o w . . h i g h ] A[low..high] A[low..high],首先寻找跨越中央位置的最大子数组,然后分别递归求解 A [ l o w . . m i d ] A[low..mid] A[low..mid] A [ m i d + 1.. h i g h ] A[mid+1..high] A[mid+1..high]的最大子数组,比较这三种情况的最大子数组,从中选出元素和最大者作为 A [ l o w . . h i g h ] A[low..high] A[low..high]的最大子数组。
  分治法的关键在于寻找跨越中央位置的最大子数组。对于一个子数组 A [ l o w . . h i g h ] A[low..high] A[low..high],任何跨越中央位置 m i d mid mid的子数组必然都由两个子数组 A [ i . . m i d ] A[i..mid] A[i..mid] A [ m i d + 1.. j ] A[mid+1..j] A[mid+1..j]组成,其中 l o w ≤ i ≤ m i d low ≤ i ≤ mid lowimid并且 m i d < j ≤ h i g h mid < j ≤ high mid<jhigh。因此,我们只需要找出形如 A [ i . . m i d ] A[i..mid] A[i..mid]和A [ m i d + 1.. j ] [mid+1..j] [mid+1..j]的最大子数组,然后将二者合并即可。下面给出寻找跨越中央位置的最大子数组的伪代码。
  在这里插入图片描述
  接下来给出分治法求解最大数组问题的伪代码。
   在这里插入图片描述
  要寻找数组 A [ 1.. n ] A[1..n] A[1..n]的最大子数组,只需要调用FIND-MAXIMUM -SUBARRAY ( A , 1 , n ) (A, 1, n) (A,1,n)即可。
  下面分析分治法求解最大子数组问题的时间复杂度。对于长度为 n n n的数组,求解最大子数组的时间用 T ( n ) T(n) T(n)表示。 T ( n ) T(n) T(n)由三部分组成:
  • 递归求解子数组 A [ 1.. m i d ] A[1..mid] A[1..mid]的最大子数组的时间 T ( n / 2 ) T(n/2) T(n/2)
  • 递归求解子数组 A [ m i d + 1.. n ] A[mid+1..n] A[mid+1..n]的最大子数组的时间 T ( n / 2 ) T(n/2) T(n/2)
  • 求解跨越中央位置的最大子数组的时间,这一时间为 Θ ( n ) Θ(n) Θ(n)
  所以有递归式 T ( n ) = 2 T ( n / 2 ) + Θ ( n ) T(n) = 2T(n/2) +Θ(n) T(n)=2T(n/2)+Θ(n)。求解这个递归式,得到 T ( n ) = Θ ( n l g n ) T(n) = Θ(nlgn) T(n)=Θ(nlgn)

练习

4.1-1 A A A的所有元素均为负数时,FIND-MAXIMUM-SUBARRAY返回什么?
  
  返回数值最大的那个负数,即绝对值最小的负数。
  
4.1-2 对最大子数组问题,编写暴力求解方法的伪代码,其运行时间应该为 Θ ( n 2 ) Θ(n^2) Θ(n2)
  
  在这里插入图片描述
  
4.1-3 当你的计算机上实现最大子数组问题的暴力算法和递归算法。请指出多大的问题规模 n 0 n_0 n0是性能交叉点——从此之后递归算法将击败暴力算法?然后,修改递归算法的基本情况——当问题规模小于 n 0 n_0 n0时采用暴力算法。修改后,性能交叉点会改变吗?
  

4.1-4 假定修改最大子数组问题的定义,允许结果为空子数组,其和为 0 0 0。你应该如何修改现有算法,使它们能允许空子数组为最终结果?
  
  先对整个数组遍历一遍,检查是否所有元素都为负数。如果所有元素都为负数,则算法输出空子数组。如果数组中存在正数,则调用FIND-MAXIMUM –SUBARRAY求解。

4.1-5 使用如下思想为最大子数组问题设计一个非递归的、线性时间的算法。从数组的左边界开始,由左至右处理,记录到目前为止已经处理过的最大子数组。若已知 A [ 1.. j ] A[1..j] A[1..j]的最大子数组,基于如下性质将解扩展为 A [ 1.. j + 1 ] A[1..j+1] A[1..j+1]的最大子数组: A [ 1.. j + 1 ] A[1..j+1] A[1..j+1]的最大子数组要么是 A [ 1.. j ] A[1..j] A[1..j]的最大子数组,要么是某个形如 A [ i . . j + 1 ] A[i..j+1] A[i..j+1]的最大子数组 ( 1 ≤ i ≤ j + 1 ) (1 ≤ i ≤ j+1) (1ij+1)。在已知形如 A [ i . . j ] A[i..j] A[i..j]的最大子数组的情况下,可以在常数时间内找出形如 A [ i . . j + 1 ] A[i..j+1] A[i..j+1]的最大子数组。
  
  与分治法不同,这是典型的增量法。本题的关键在于:在已知以 A [ j ] A[j] A[j]结尾的最大子数组的情况下,找出以 A [ j + 1 ] A[j+1] A[j+1]结尾的最大子数组。假设以 A [ j ] A[j] A[j]结尾的最大数组为 A [ i . . j ] ( 1 ≤ i ≤ j ) A[i..j] (1 ≤ i ≤ j) A[i..j](1ij)。分两种情况:
  (1) 如果 A [ i . . j ] A[i..j] A[i..j]各元素之和 s u m { A [ i . . j ] } > 0 {\rm sum}\{A[i..j]\} > 0 sum{A[i..j]}>0,那么以 A [ j + 1 ] A[j+1] A[j+1]结尾的最大数组为 A [ i . . j + 1 ] A[i..j+1] A[i..j+1]。这一点可以用反证法来说明。假设以 A [ j + 1 ] A[j+1] A[j+1]结尾的最大数组为 A [ k . . j + 1 ] A[k..j+1] A[k..j+1],其中 1 ≤ k ≤ j + 1 1 ≤ k ≤ j+1 1kj+1并且 k ≠ i k ≠ i k=i。又分两种情况讨论。
  1) 1 ≤ k ≤ j 1 ≤ k ≤ j 1kj:由于以 A [ j ] A[j] A[j]结尾的最大子数组为 A [ i . . j ] A[i..j] A[i..j],所以 s u m { A [ k . . j ] } ≤ s u m { A [ i . . j ] } {\rm sum}\{A[k..j]\} ≤ {\rm sum}\{A[i..j]\} sum{A[k..j]}sum{A[i..j]},从而有 s u m { A [ k . . j + 1 ] } ≤ s u m { A [ i . . j + 1 ] } {\rm sum}\{A[k..j+1]\} ≤ {\rm sum}\{A[i..j+1]\} sum{A[k..j+1]}sum{A[i..j+1]}。如果 s u m { A [ k . . j + 1 ] } < s u m { A [ i . . j + 1 ] } {\rm sum}\{A[k..j+1]\} < {\rm sum}\{A[i..j+1]\} sum{A[k..j+1]}<sum{A[i..j+1]},那么 A [ k . . j + 1 ] A[k..j+1] A[k..j+1]肯定不是以 A [ j + 1 ] A[j+1] A[j+1]结尾的最大数组,这与假设矛盾。如果 s u m { A [ k . . j + 1 ] } = s u m { A [ i . . j + 1 ] } {\rm sum}\{A[k..j+1]\} = {\rm sum}\{A[i..j+1]\} sum{A[k..j+1]}=sum{A[i..j+1]},那么如果假设成立,即 A [ k . . j + 1 ] A[k..j+1] A[k..j+1]是以 A [ j + 1 ] A[j+1] A[j+1]结尾的最大数组,那么 A [ i . . j + 1 ] A[i..j+1] A[i..j+1]也同样是以 A [ j + 1 ] A[j+1] A[j+1]结尾的最大数组。
  2) k = j k = j k=j:此时假设的以 A [ j + 1 ] A[j+1] A[j+1]结尾的最大数组为 A [ j + 1 ] A[j+1] A[j+1]本身。由于 s u m { A [ i . . j ] } > 0 {\rm sum}\{A[i..j]\} > 0 sum{A[i..j]}>0,所以 s u m { A [ i . . j + 1 ] } > A [ j + 1 ] {\rm sum}\{A[i..j+1]\} > A[j+1] sum{A[i..j+1]}>A[j+1]。这说明 A [ j + 1 ] A[j+1] A[j+1]本身肯定也不是以 A [ j + 1 ] A[j+1] A[j+1]结尾的最大数组,这与假设矛盾。
  (2) 如果 A [ i . . j ] A[i..j] A[i..j]各元素之和 s u m { A [ i . . j ] } ≤ 0 {\rm sum}\{A[i..j]\} ≤ 0 sum{A[i..j]}0,那么 A [ j + 1 ] A[j+1] A[j+1]结尾的最大数组为 A [ j + 1 ] A[j+1] A[j+1]本身。这一点同样可以用反证法来说明,这里就不赘述。
  下面给出该算法的伪代码。
  在这里插入图片描述
  对于一个包含 n n n个元素的数组,该算法一共包含 n n n次迭代,每次迭代花费 Θ ( 1 ) Θ(1) Θ(1)时间。因此,该算法的运行时间为 Θ ( n ) Θ(n) Θ(n)
  
  本节代码链接:
  https://github.com/yangtzhou2012/Introduction_to_Algorithms_3rd/tree/master/Chapter04/Section_4.1

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值