中心思想
- 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))
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。