算法导论读书笔记 第4章 分治策略

来源:互联网 发布:linux控制台中文乱码 编辑:程序博客网 时间:2024/05/22 14:45

 在第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 == low2         return(low, high, A[low])3     else mid = (low + high) / 24         (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-num8                    return (left-low, left-high, left-sum)9        elseif right-sum >= left-sum and rigth-sum >=cross-sum10                  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)。
0 0