第四章 分治策略 4.1 最大子数组问题

4.1 最大子数组问题

一.

  分治策略中,递归地求解一个问题,在每层递归中应用如下三个步骤:

  • 分解 步骤将问题划分为一些子问题,子问题的形式与原问题一样,只是规模更小。
  • 解决 步骤递归地求解出子问题。如果子问题的规模足够小,则停止递归,直接求解。
  • 合并 步骤将子问题的解组合成原问题的解。

  当子问题足够大,需要递归求解时,我们称之为递归情况。当子问题变得足够小,不再需要递归时,我们说递归已经“触底”,进入了基本情况


  子问题的规模不必是原问题规模的一个固定比例。
  存在不是等式而是不等式的递归式,例如 T ( n ) ≤ 2 T ( n / 2 ) + Θ ( n ) T(n)\leq 2T(n/2)+\Theta(n) T(n)2T(n/2)+Θ(n) 。对此可用大 O O O 符号而不是 Θ \Theta Θ 符号来描述其解。类似地如 T ( n ) ≥ 2 T ( n / 2 ) + Θ ( n ) T(n)\geq 2T(n/2)+\Theta(n) T(n)2T(n/2)+Θ(n) 应该用 Ω \Omega Ω 符号来描述其解。

二.

  已知一存在负数的数组,求解最大子数组。我们采用分治法设计一个 o ( n 2 ) o(n^2) o(n2) 的算法。
  原问题如图所示,在这里插入图片描述
  我们考察每日的价格变化,第 i i i 天的价格变化定义为第 i i i 天和第 i − 1 i-1 i1 天的价格差,则问题转化为寻找 A A A 的和最大的非空连续子数组。我们成这样的连续子数组为最大子数组
  只有当数组中包含负数时,最大子数组问题才有意义。如果所有数组元素都是非负的,最大子数组问题没有任何难度,因为整个数组的和肯定是最大的。


  使用分治技术,即将子数组划分为两个规模尽量相等的子数组。找到子数组的中央位置,比如 m i d mid mid ,然后考虑求解两个子数组 A [ l o w .   .   m i d ] A[low.\space .\space mid] A[low. . mid]左子数组)和 A [ m i d + 1.   .   h i g h ] A[mid + 1.\space .\space high] A[mid+1. . high]右子数组)。于是 A [ l o w .   .   h i g h ] A[low.\space .\space high] A[low. . high] 的任何连续子数组 A [ i .   .   j ] A[i.\space .\space j] A[i. . j] 所处的位置必然是:

  • 完全位于子数组 A [ l o w .   .   m i d ] A[low.\space .\space mid] A[low. . mid] 中,因此 l o w ≤ i ≤ j ≤ m i d low\leq i\leq j \leq mid lowijmid
  • 完全位于子数组 A [ m i d + 1.   .   h i g h ] A[mid + 1.\space .\space high] A[mid+1. . high] 中,因此 m i d < i ≤ j ≤ h i g h mid<i\leq j\leq high mid<ijhigh
  • 跨越了中点,因此 l o w ≤ i ≤ m i d < j ≤ h i g h low\leq i \leq mid<j\leq high lowimid<jhigh

  我们可以递归地求解 A [ l o w .   .   m i d ] A[low.\space .\space mid] A[low. . mid] A [ m i d + 1.   .   h i g h ] A[mid +1.\space .\space high] A[mid+1. . high] 的最大子数组,因为这两个子问题仍是最大子数组问题,只是规模更小。因此剩下的工作就是寻找跨越中点的最大子数组,然后在三种情况中选取和最大者。
  由于求出的子数组必须跨越中点这一限制,寻找跨越中点的最大子数组并非是原问题的实例。我们很容易在线性时间(相对于子数组 A [ l o w .   .   h i g h ] A[low.\space .\space high] A[low. . high] 的规模)内求出跨越中点的最大子数组。只需找出形如 A [ i .   .   m i d ] A[i.\space .\space mid] A[i. . mid] A [ m i d + 1.   .   j ] A[mid+1.\space .\space j] A[mid+1. . j] 的最大子数组,然后将其合并即可。
FIND-MAX-CROSSING-SUBARRAY(A, low, mid, high)

left-sum = -∞
sum = 0
for i = mid downto low
	sum = sum + A[i]
	if sum > left-sum
		left-sum = sum
		max-left = i
right-sum = -∞
sum = 0
for j = mid + 1 to high
	sum = sum + A[j]
	if sum > right-sum
		right-sum = sum
		max-right = j
return (max-left, max-left, left-sum + right-sum)

  如果子数组 A [ l o w .   .   h i g h ] A[low.\space.\space high] A[low. . high] 包含 n n n 个元素(即 n = h i g h − l o w + 1 n=high-low+1 n=highlow+1),则调用 FIND-MAX-CROSSING-SUBARRAY(A, low, mid, high) 花费 Θ ( n ) \Theta(n) Θ(n) 时间。
  而最大子数组问题的分治算法的伪代码如下:
FIND-MAXINUM-SUBARRAY(A, low, high)

if high == low
	return (low, high, A[low])                                //base case: only one element
else mid =(low+high)/2(left-low, left-high, left-sum) = 
		FIND-MAXINUM-SUBARRAY(A, low, mid)
	(right-low, right-high, right-sum) = 
		FIND-MAXINUM-SUBARRAY(A, mid + 1, high)
	(cross-low, cross-high, cross-sum) = 
		FIND-MAX-CROSSING-SUBARRAY(A, low, mid, high)
	if left-sum ≥ right-sum and left-sum ≥ cross-sum
		return (left-low, left-high, left-sum)
	elseif right-sum ≥ left-sum and right-sum ≥ cross-sum
		return (right-low, right-high, right-sum)
	else return (cross-low, cross-high, cross-sum)

  初始调用 FIND-MAXINUM-SUBARRAY(A, 1, A.length) 会求出 A [ 1.   .   n ] A[1. \space.\space n] A[1. . n] 的最大子数组。
  对原问题简化,假设原问题的规模为 2 的幂,这样所有子问题的规模均为整数。我们用 T ( n ) T(n) T(n) 表示FIND-MAXINUM-SUBARRAY 求解 n n n 个元素的最大子数组的运行时间。分析有: T ( n ) = { Θ ( 1 ) 若 n = 1 2 T ( n / 2 ) + Θ ( n ) 若 n > 1 T(n)=\begin{cases}\Theta(1)&\text{若 n = 1}\\2T(n/2)+\Theta(n)&\text{若 n > 1}\end{cases} T(n)={Θ(1)2T(n/2)+Θ(n) n = 1 n > 1  显然解为 T ( n ) = n lg ⁡ n T(n)=n\lg n T(n)=nlgn

三.

4. 1-1

  当 A A A 的所有元素均为负数时, FIND-MAXINUM-SUBARRAY 返回什么?

  解:返回其中元素的最大值。

4. 1-2

  对最大子数组问题,编写暴力求解方法的伪代码, 其运行时间应该为 Θ ( n 2 ) \Theta(n^2) Θ(n2)

  解:代码如下:

BRUTE-FORCE-FIND-MAXIMUM-SUBARRAY(A)

n = A.length
max-sum = -for l = 1 to n
    sum = 0
    for h = l to n
        sum = sum + A[h]
        if sum > max-sum
            max-sum = sum
            low = l
            high = h
return (low, high, max-sum)

4. 1-3

  在你的计算机上实现最大子数组问题的暴力算法和递归算法。请指出多大的问题规模 n 0 n_0 n0 是性能交叉点——从此之后递归算法将击败暴力算法?然后,修改递归算法的基本情况——当问题规模小于 n 0 n_0 n0 时采用暴力算法。修改后,性能交叉点会改变吗?

  解: n 0 n_0 n0 与计算机种类等因素有关,这里略去。
    如果将该算法修改为 n ≥ n 0 n\geq n_0 nn0 时采用分治法, n n n 较小时采用蛮力法,则在交点处的性能几乎翻倍。在 n 0 − 1 n_0 - 1 n01 处的性能保持不变(甚至更差,因为增加了开销)。

4. 1-4

  假定修改最大子数组问题的定义,允许结果为空子数组,其和为 0 。你应该如何修改现有算法,使它们能允许空子数组为最终结果?

  解:在算法的最后判断最大子数组的总和是否小于零,如果小于零则返回空子数组。

4. 1-5

  使用如下思想为最大子数组问题设计一个非递归的、线性时间的算法。 从数组的左边界开始,由左至右处理,记录到目前为止已经处理过的最大子数组。若已知 A [ 1.   .   j ] A[1.\space.\space j] A[1. . j] 的最大子数组,基于如下性质将其扩展为 A [ 1.   .   j + 1 ] A[1.\space.\space j + 1] A[1. . j+1] 的最大子数组: A [ 1.   .   j + 1 ] A[1.\space.\space j+1] A[1. . j+1] 的最大子数组要么是 A [ 1.   .   j ] A[1.\space.\space j] A[1. . j] 的最大子数组,要么是某个子数组 A [ i .   .   j + 1 ] ( 1 ≤ i ≤ j + 1 ) A[i.\space.\space j+1](1\leq i\leq j+1) A[i. . j+1](1ij+1)。在已知 A [ 1.   .   j ] A[1.\space.\space j] A[1. . j] 最大子数组的情况下,可以在线性时间内找出形如 A [ i .   .   j + 1 ] A[i.\space.\space j+1] A[i. . j+1] 的最大子数组。

  解:代码如下:
ITERATIVE-FIND-MAXIMUM-SUBARRAY(A)

n = A.length
max-sum = -∞
sum = -for j = 1 to n
    currentHigh = j
    if sum > 0
        sum = sum + A[j]
    else
        currentLow = j
        sum = A[j]
    if sum > max-sum
        max-sum = sum
        low = currentLow
        high = currentHigh
return (low, high, max-sum)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值