笔记

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

练习

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

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

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

相关文章: