笔记
本节给出了分治法的一个例子。给定一个数组A[1..n],找出一个元素和为最大的连续子数组A[i..j],其中1≤i≤j≤n,称这样的子数组为最大子数组。例如,下图所示数组中,第8个元素到第11个元素之间的子数组为最大子数组。

求解最大子数组问题,最简单的方法是暴力检查所有的子数组,从中找出和为最大的子数组。对于一个有n个元素的数组,一共有Cn2+Cn1=n(n−1)/2+n=Θ(n2)个子数组。参考练习4.1-2可知,计算每个子数组的和只需要O(1)时间。因此暴力求解法的所花费的时间为Θ(n2)。
除了暴力求解法,最大子数组问题还可以使用分治法求解,并且分治法具有更优的时间复杂度。假定要寻找子数组A[low..high]的最大子数组,我们从中央位置mid=⌊(low+high)/2⌋将A[low..high]划分为两个子数组,A[low..mid]和A[mid+1..high]。于是,A[low..high]的任何一个子数组A[i..j]必然是以下三种情况之一:
• 完全位于子数组A[low..mid]中,即low≤i≤j≤mid。
• 完全位于子数组A[mid+1..high]中,即mid<i≤j≤high。
• 跨越了中央位置mid,即low≤i≤mid<j≤high。
根据以上分析,可以递归求解最大子数组问题。对于一个子数组A[low..high],首先寻找跨越中央位置的最大子数组,然后分别递归求解A[low..mid]和A[mid+1..high]的最大子数组,比较这三种情况的最大子数组,从中选出元素和最大者作为A[low..high]的最大子数组。
分治法的关键在于寻找跨越中央位置的最大子数组。对于一个子数组A[low..high],任何跨越中央位置mid的子数组必然都由两个子数组A[i..mid]和A[mid+1..j]组成,其中low≤i≤mid并且mid<j≤high。因此,我们只需要找出形如A[i..mid]和A[mid+1..j]的最大子数组,然后将二者合并即可。下面给出寻找跨越中央位置的最大子数组的伪代码。

接下来给出分治法求解最大数组问题的伪代码。

要寻找数组A[1..n]的最大子数组,只需要调用FIND-MAXIMUM -SUBARRAY(A,1,n)即可。
下面分析分治法求解最大子数组问题的时间复杂度。对于长度为n的数组,求解最大子数组的时间用T(n)表示。T(n)由三部分组成:
• 递归求解子数组A[1..mid]的最大子数组的时间T(n/2);
• 递归求解子数组A[mid+1..n]的最大子数组的时间T(n/2);
• 求解跨越中央位置的最大子数组的时间,这一时间为Θ(n)。
所以有递归式T(n)=2T(n/2)+Θ(n)。求解这个递归式,得到T(n)=Θ(nlgn)。
练习
4.1-1 当A的所有元素均为负数时,FIND-MAXIMUM-SUBARRAY返回什么?
解
返回数值最大的那个负数,即绝对值最小的负数。
4.1-2 对最大子数组问题,编写暴力求解方法的伪代码,其运行时间应该为Θ(n2)。
解

4.1-3 当你的计算机上实现最大子数组问题的暴力算法和递归算法。请指出多大的问题规模n0是性能交叉点——从此之后递归算法将击败暴力算法?然后,修改递归算法的基本情况——当问题规模小于n0时采用暴力算法。修改后,性能交叉点会改变吗?
略
4.1-4 假定修改最大子数组问题的定义,允许结果为空子数组,其和为0。你应该如何修改现有算法,使它们能允许空子数组为最终结果?
解
先对整个数组遍历一遍,检查是否所有元素都为负数。如果所有元素都为负数,则算法输出空子数组。如果数组中存在正数,则调用FIND-MAXIMUM –SUBARRAY求解。
4.1-5 使用如下思想为最大子数组问题设计一个非递归的、线性时间的算法。从数组的左边界开始,由左至右处理,记录到目前为止已经处理过的最大子数组。若已知A[1..j]的最大子数组,基于如下性质将解扩展为A[1..j+1]的最大子数组:A[1..j+1]的最大子数组要么是A[1..j]的最大子数组,要么是某个形如A[i..j+1]的最大子数组(1≤i≤j+1)。在已知形如A[i..j]的最大子数组的情况下,可以在常数时间内找出形如A[i..j+1]的最大子数组。
解
与分治法不同,这是典型的增量法。本题的关键在于:在已知以A[j]结尾的最大子数组的情况下,找出以A[j+1]结尾的最大子数组。假设以A[j]结尾的最大数组为A[i..j](1≤i≤j)。分两种情况:
(1) 如果A[i..j]各元素之和sum{A[i..j]}>0,那么以A[j+1]结尾的最大数组为A[i..j+1]。这一点可以用反证法来说明。假设以A[j+1]结尾的最大数组为A[k..j+1],其中1≤k≤j+1并且k=i。又分两种情况讨论。
1) 1≤k≤j:由于以A[j]结尾的最大子数组为A[i..j],所以sum{A[k..j]}≤sum{A[i..j]},从而有sum{A[k..j+1]}≤sum{A[i..j+1]}。如果sum{A[k..j+1]}<sum{A[i..j+1]},那么A[k..j+1]肯定不是以A[j+1]结尾的最大数组,这与假设矛盾。如果sum{A[k..j+1]}=sum{A[i..j+1]},那么如果假设成立,即A[k..j+1]是以A[j+1]结尾的最大数组,那么A[i..j+1]也同样是以A[j+1]结尾的最大数组。
2) k=j:此时假设的以A[j+1]结尾的最大数组为A[j+1]本身。由于sum{A[i..j]}>0,所以sum{A[i..j+1]}>A[j+1]。这说明A[j+1]本身肯定也不是以A[j+1]结尾的最大数组,这与假设矛盾。
(2) 如果A[i..j]各元素之和sum{A[i..j]}≤0,那么A[j+1]结尾的最大数组为A[j+1]本身。这一点同样可以用反证法来说明,这里就不赘述。
下面给出该算法的伪代码。

对于一个包含n个元素的数组,该算法一共包含n次迭代,每次迭代花费Θ(1)时间。因此,该算法的运行时间为Θ(n)。
本节代码链接:
https://github.com/yangtzhou2012/Introduction_to_Algorithms_3rd/tree/master/Chapter04/Section_4.1