中心思想

  • Divide 分解规模较小且问题形式相同的多个子问题
  • Conquer 子问题继续递归后能被求解
  • Combine 合并解组合

栗子:求最大子数组问题的分治思想

条件与返回值

输入源:数组A[low..high]
返回:A[i..j] 要求 A[i] + A[i+1].. + A[j] = MAX

分治模式

1.尽可能的将原数组分解为规模大致相同的两个子数组(Divide),找到数组的中间项mid

2.原问题与子问题的解的可能性都为三种:

  • 解完全位于A[low..mid]中,故当前规模的解为:low <= i <= j <= mid。
  • 解完全位于A[mid+1..high]中,故当前规模的解为:mid < i <= j <= high。
  • 跨中点,故当前规模的解为:low <= i <= mid < j <= high。

若解在A[low..mid]或者是A[mid+1..high]中,那么这两种情况仍然是输入规模较小的最大数组问题,因此仍需要进行递归。当解跨中点的时候,递归终止(Conquer)。

3.选取和最大者(Combine)。

伪代码

FIND-MAX-CROSSING-SUBARRAY(A, low, mid, high)
max-left = low
max-right = 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-right, left-sum + right-sum)

如果A[low..high]包含n个元素,那么调用FIND-MAX-CROSSING-SUBARRAY共花费Θ(n)时间。for循环每次迭代花费Θ(1)时间,总消耗即为迭代次数(mid - low + 1) + (high - mid) = n。

FIND-MAXIMUM-SUBARRAY(A, low, high)
// 跨中点特例 原子问题递归终止
if high == low
    return (low, high, A[low])
else mid = floor((low + high) / 2)
    // 解在左半边的递归
    (left-low, left-high, left-sum) = FIND-MAXIMUM-SUBARRAY(A, low, mid)

    // 解在右半边的递归
    (right-low, right-high, right-sum) = FIND-MAXIMUM-SUBARRAY(A, mid + 1, high)

    // 解跨中间的递归
    (cross-low, cross-high, cross-sum) = FIND-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)

算法分析

当问题规模n为1的时候,消耗时间为Θ(1),若不然进行问题拆分成左右两个子数组,消耗时间的组成是左、右数组规模消耗时间,FIND-CROSSING-SUBARRAY函数的调用时间以及最后判断最大值的时间,由此我们可以得出:

T(n) = Θ(1) + 2T(n/2) + Θ(n) + Θ(1) = 2T(n/2) + Θ(n)

整个算法复杂度为T(n) = Θ(nlgn)

Swift实现

import Foundation

func findMaxCrossingSubarray(arr: [Int], low: Int, mid: Int, high: Int) -> (max_left: Int, max_right: Int, sum: Int) {

    var max_left = low
    var max_right = high
    var left_sum = -100000
    var right_sum = -100000
    var sum = 0

    for var index = mid; index >= low; --index {
        sum = sum + arr[index]
        if sum > left_sum {
            left_sum = sum
            max_left = index
        }
    }

    sum = 0
    for var index = mid + 1; index <= high; ++index {
        sum = sum + arr[index]
        if sum > right_sum {
            right_sum = sum
            max_right = index
        }
    }

    return (max_left, max_right, left_sum + right_sum)
}




func findMaximumSubarray(arr: [Int], low: Int, high: Int) -> (max_left: Int, max_right: Int, sum: Int) {

    var left_low, left_high, left_sum: Int
    var right_low, right_high, right_sum: Int
    var cross_low, cross_high, cross_sum: Int
    var mid: Int

    if high == low {
        return (low, high, arr[low])
    } else {
        mid = (low + high) / 2

        (left_low, left_high, left_sum) = findMaximumSubarray(arr, low, mid)
        (right_low, right_high, right_sum) = findMaximumSubarray(arr, mid + 1, high)
        (cross_low, cross_high, cross_sum) = findMaxCrossingSubarray(arr, low, mid, high)
    }

    if left_sum >= right_sum && left_sum >= cross_sum {
        return (left_low, left_high, left_sum)
    } else if right_sum >= left_sum && right_sum >= cross_sum {
        return (right_low, right_high, right_sum)
    } else {
        return (cross_low, cross_high, cross_sum)
    }
}




var arrayForTest: [Int] = [13, -3, -25, 20, -3, -16, -23, 18, 20, -7, 12, -5, -22, 15, -4, 7]

print (findMaximumSubarray(arrayForTest, 0, arrayForTest.count - 1))

Cruise_Chan
729 声望71 粉丝

技能树点歪了...咋办