在第2章中,归并排序算法使用了分治策略。即在分治策略中,递归地求解一个问题,在每层递归中应包含三个步骤:
分解(Divide)步骤将问题画分为一些子问题,子问题的形式与原问题一样,只是规模更小。
解决(Conquer)步骤递归地求解出子问题。如果子问题的规模足够小,则停止递归,直接求解。
合并(Combine)步骤将子问题的解组合成原问题的解。
当子问题足够大时,需要递归求解时,我们称之为递归情况(recursive case)。当子问题变得足够小,不需要在递归时,此时已进入基本情况(base case)。有时,除了与原问题形式完全一样的规模更小的子问题外,还需求解与原问题不完全一样的子问题,但这种情况可以看做是合并步骤的一部分。本章中会看到两个基于分治策略的算法。其中一个是求解最大子数组问题,其输入是一个数值数组,算法需要确定有最大和的连续子数组。
递归式
递归式是与分治法紧密相关的,因为递归式子可以很自然地刻画分治算法的运行时间。一个递归时就是一个等式或不等式,它通过更小的输入上的函数值来描述一个函数。用递归式描述MERGE-SORT过程的最坏运行时间为T(n):
求解可得T(n) = O(nlgn)。
4.1 最大子数组问题
最大子数组问是指需找数组中和最大的非空连续子数组,称这样的子数组为最大子数组(maximum subarray)。如下图数组A中,A[1.. 16]的最大子数组为A[8.. 11],其和为43。
最大子数组中只有包含负数时才有意义,如果所有数组元素都是非负的,最大子数组问题没有任何难度,以为整个数组的和可定是最大的。
使用分治策略求解最大子数组问题
假定要寻找子数组A[low.. high]的最大子数组。使用分治策略意味着要将数组划分为两个规模尽量相等的子数组。即找到子数组的中央位置,比如mid,然后考虑求解两个子数组A[low.. mid]和A[mid + 1.. high]。如下图所示,A[low.. high]的任意连续子数组A[i.. j]所处的位置必然是如下三种情况之一:
1 完全位于子数组A[low.. mid]中, 因此 low <= i <= j <= mid
2 完全位于子数组A[mid + 1, high]中, 因此 mid < i <= j <= high
3 跨越了中点, 因此low <= i <= mid < j <=high
因此,A[low.. high]的最大子数组所处的位置必然是这三种情况之一。通过调用FIND-MAX-CROSSING-SUBARRAY接受数组A和下标low,mid和high为输入,返回一个下标元组划定跨越中点的最大子数组的边界,并返回最大子数组中的和。为代码如下:(-INT_MAX表示负的无穷大)
Find-Max-Crossing-SUBARRAY(A, low, mid, high):
left-sum = -INT_MAX
sum = 0
for i =mid downto low:
sum = sum + A[i]
if sum > left-sum:
left-sum = sum
max-left = i
right-sum = -INT_MAX
sum = 0
max-right = 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-right, left-sum + right-sum)
(由于伪代码中返回值时有多个参数,突然不知道用c/c++语言怎么实现,故采用python语言实现。欢迎各位高手用c/c++语言实现该算法)采用python代码实现的完整程序为:
def findMaxCrossingSubArray(A, low, mid, high):
leftSum = -65536
sum2 = 0
maxLeft = 0
for i in range(mid, low, -1):
sum2 = sum2 + A[i]
if sum2 > leftSum:
leftSum = sum2
maxLeft = i
rightSum = -65536
sum2 = 0
maxRight = 0
for j in range(mid+1, high):
sum2 = sum2 + A[j]
if sum2 > rightSum:
rightSum = sum2
maxRight = j
return maxLeft, maxRight, leftSum + rightSum
FIND-MAX-CROSSING-SUBARRAY的运行时间为线性时间,则最大子数组问题的分治法算法的伪代码如下:
FIND-MAXIMUM-SUBARRAY(A, low, high)
1 if high == low
2 return(low, high, A[low])
3 else mid = (low + high) / 2
4 (left-low, left-high, left-sum) = FIND-MAXIMUM-SUBARRAY(A, low, mid)
5 (right-low, right-high, right-sum) = FIIND-MAXIMUM-SUBARRAY(A, mid+1, high)
6 (cross-low, cross-high, cross-sum) = FIND-MAXMUM-SUBARRAY(A, low, mid, higt)
7 if left-sum>=right-sum and left-sum >= cross-num
8 return (left-low, left-high, left-sum)
9 elseif right-sum >= left-sum and rigth-sum >=cross-sum
10 return(right-low, right-high, right-sum)
11 else return (cross-low, cross-high, cross-sum)
初始调用FIND-MAXIMUM-SUBARRAY(A, 1, A.Length)会求出A[1.. n]的最大子数组。
pyhton语言实现的完整程序为:
def findMaximumSubArrary(A, low, high):
if high == low:
return low, high, A[low]
else:
mid = (low + high) / 2
leftLow, leftHigh, leftSum = findMaximumSubArrary(A, low, mid)
rightLow, rightHigh, rightSum = findMaximumSubArrary(A, mid + 1, high)
crossLow, crossHigh, crossSum = findMaxCrossingSubArray(A, low, mid, high)
if leftSum>=rightSum and leftSum>=crossSum:
return leftLow, leftHigh, leftSum
elif rightSum>=leftSum and rightSum>=crossSum:
return rightLow, rightHigh, rightSum
else:
return crossLow, crossHigh, crossSum
寻找最大子数组的递归式与归并排序的递归式是相同的,故算法时间复杂度为O(nlgn)。