最大子数组问题分治算法的伪代码【 Θ ( n lg n ) \Theta(n\lg n) Θ(nlgn)】:
FIND-MAXIMUM-SUBARRAY(A,low,high)
if high==low
return (low,high,A[low])
else
mid=⌊(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-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-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_right,left_sum+right_sum)
再来看暴力算法的伪代码【 Θ ( n 2 ) \Theta(n^2) Θ(n2)】:
FIND-MAXIMUM-ARRAY-BRUTE-FORCE(A,low,high)
max_sum=-∞
for i=low to high
sum=A[i]
for j=i+1 to high
sum=sum+A[j]
if sum>max_sum
max_sum=sum
index_low=i
index_high=j
return (index_low,index_high,max_sum)
下面用C++实现上述伪代码并在main()函数中求取两种算法的性能交叉点n0——当问题规模大于n0时分治算法击败暴力算法。
#include <iostream>
#include <tuple>
#include <ctime>
#include <limits.h>
using namespace std;
tuple<int, int, int> FIND_MAX_CROSSING_SUBARRAY(int A[], int low, int mid, int high) {
int left_sum = INT_MIN, right_sum = INT_MIN;
int sum = 0, max_left, max_right;
for (int i = mid; i >= low; i--) {
sum += A[i];
if (sum > left_sum) {
left_sum = sum;
max_left = i;
}
}
sum = 0;
for (int j = mid + 1; j <= high; j++) {
sum += A[j];
if (sum > right_sum) {
right_sum = sum;
max_right = j;
}
}
return make_tuple(max_left, max_right, left_sum + right_sum);
}
tuple<int, int, int> FIND_MAXIMUM_SUBARRAY(int A[], int low, int high) {
if (high == low)
return make_tuple(low, high, A[low]);
else {
int mid = (low + high) / 2;
tuple<int, int, int> left = FIND_MAXIMUM_SUBARRAY(A, low, mid);
//(left_low,left_high,left_sum)
tuple<int, int, int> right = FIND_MAXIMUM_SUBARRAY(A, mid + 1, high);
//(right_low,right_high,right_sum)
tuple<int, int, int> cross = FIND_MAX_CROSSING_SUBARRAY(A, low, mid, high);
//(cross_low,cross_high,cross_sum)
if (get<2>(left) >= get<2>(right) && get<2>(left) >= get<2>(cross))
return left;
else if (get<2>(right) >= get<2>(left) && get<2>(right) >= get<2>(cross))
return right;
else
return cross;
}
}
tuple<int, int, int> FIND_MAXIMUM_SUBARRAY_BRUTE_FORCE(int A[], int low, int high) {
int max_sum = INT_MIN;
int max_left, max_right;
for (int i = low; i <= high; i++) {
int sum = A[i];
for (int j = i+1; j <= high; j++) {
sum += A[j];
if (sum > max_sum) {
max_sum = sum;
max_left = i;
max_right = j;
}
}
}
return make_tuple(max_left, max_right, max_sum);
}
int main() {
const int MAX_N = 10000, LOOP_TIME = 1000;
int A[MAX_N], n_sum = 0;
srand(time(NULL));
for(int j = 0; j < LOOP_TIME; j++)
// loop for LOOP_TIME times to get the average crossover point
for (int n = 2; n <= MAX_N; n++) {
for (int i = 0; i < n; i++)
A[i] = rand() % 201 - 100; // fill A with random integers between -100 and 100
clock_t start_brute=clock();
tuple<int, int, int> result_brute=FIND_MAXIMUM_SUBARRAY_BRUTE_FORCE(A, 0, n-1);
clock_t end_brute=clock();
clock_t start=clock();
tuple<int, int, int> result=FIND_MAXIMUM_SUBARRAY(A, 0, n-1);
clock_t end=clock();
if (end-start < end_brute-start_brute) {
n_sum+=n;
break;
}
}
int n_average = n_sum / LOOP_TIME;
cout << "Average crossover point: " << n_average << endl;
return 0;
}
/*
int main(){
//main function for test
int A[] = { 13, -3, -25, 20, -3, 16, -23, -18, -20, -7, -12, -5, -22, -15, -4, -7 };
tuple<int, int, int> result = FIND_MAXIMUM_SUBARRAY(A, 0, 15);
cout << "The maximum subarray is A[" << get<0>(result) << "..." << get<1>(result) << "], and the sum is " << get<2>(result) << endl;
return 0;
}
*/
输出显示,平均的性能交叉点n0大致在140至150之间。现在我们考虑修改分治算法的基本情况——当问题规模小于n0时采用暴力算法。取n0=145,通过将FIND_MAXIMUM_SUBARRAY
中的if (high == low) return make_tuple(low, high, A[low]);
替换成if (high - low + 1 < 145) return FIND_MAXIMUM_SUBARRAY_BRUTE_FORCE(A, low, high);
,我们得到一个改进的分治算法FIND_MAXIMUM_SUBARRAY_IMPROVED
,它在问题规模小于n0时采用暴力算法:
tuple<int, int, int> FIND_MAXIMUM_SUBARRAY_IMPROVED(int A[], int low, int high) {
if (high - low + 1 < 145)
return FIND_MAXIMUM_SUBARRAY_BRUTE_FORCE(A, low, high);
else {
int mid = (low + high) / 2;
tuple<int, int, int> left = FIND_MAXIMUM_SUBARRAY(A, low, mid);
tuple<int, int, int> right = FIND_MAXIMUM_SUBARRAY(A, mid + 1, high);
tuple<int, int, int> cross = FIND_MAX_CROSSING_SUBARRAY(A, low, mid, high);
if (get<2>(left) >= get<2>(right) && get<2>(left) >= get<2>(cross))
return left;
else if (get<2>(right) >= get<2>(left) && get<2>(right) >= get<2>(cross))
return right;
else
return cross;
}
}
相应地修改主函数中时间计算的部分,我们发现FIND_MAXIMUM_SUBARRAY_IMPROVED
击败FIND_MAXIMUM_SUBARRAY_BRUTE_FORCE
的性能交叉点提升至150~155。一个更引人注意的事实是,改进后的分治算法FIND_MAXIMUM_SUBARRAY_IMPROVED
击败改进前的分治算法FIND_MAXIMUM_SUBARRAY
的性能交叉点在100至105之间。这证实了改进的有效性。
实际应用中,很多高效的算法在问题规模较低时反而可能不如低效的实现轻快,可以考虑在性能交叉点以下用低效实现代替以进一步提升算法性能,因此对性能交叉点的关注往往裨益良多。
(参考资料:Introduction to Algorithms, Third Edition, pages 68~75)