最大子数组问题分治算法的C++实现以及对该问题分治与暴力算法性能交叉点的讨论

最大子数组问题分治算法的伪代码【 Θ ( 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 
#include 
#include 
#include 
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 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)

你可能感兴趣的:(数据结构与算法,C/C++,算法,性能优化,c++)