算法导论——最大子数组

问题描述:给定一个数组,总数组中找出一个子数组使得这个子数组中的元素的和最大。

思路:将数组进行分解成两个子数组,那么数组的最大子数组可能有三种情况。第一,完全位于左子数组中;第二,完全位于右子数组中;第三,跨越两个数组。然后取这三种子数组的最大值的子数组就是数组的最大子数组了。最后涉及到分解的截止条件,当子数组中的元素只有一个的时候,那么这个子数组本身就是最大子数组了。

下面是C++的实现

程序的开始就是判断数组分解的终止条件,当子数组中的元素只有一个的时候,那么这个子数组的最大子数组就是它本身。否则的话数组的最大子数组就分为三种情况,完全出现在左子数组中,完全出现在右子数组中,跨越两个数组。前两种情况可以通过简单的递归调用来完成。但是最后一种就要单独解决了。

typedef struct{
    int start;
    int end;
    int sum;
} result;

result max_subarray(int *array, int start, int end){

    if(start == end){
        result end_result;
        end_result.start = start;
        end_result.end = end;
        end_result.sum = array[start];
        return end_result;
    }

    result left_result, right_result, crs_result;
    int mid = (start + end) / 2;

    left_result = max_subarray(array, start, mid);
    right_result = max_subarray(array, mid + 1, end);
    crs_result = crs_max_subarray(array, start, mid, end);

    if(left_result.sum >= crs_result.sum && left_result.sum >= right_result.sum)
        return left_result;
    else if(right_result.sum >= left_result.sum && right_result.sum >= crs_result.sum)
        return right_result;
    else
        return crs_result;
}

下面就是对跨越两个子数组的情况进行处理,这种情况下最后的子数组肯定是要有元素array[mid]和array[mid + 1]的,所以寻找左子数组中包含array[mid]和右子数组中包含array[mid + 1]的最大子数组,然后这两个子数组合并起来就是这个数组的最大子数组。

result crs_max_subarray(int *array, int start, int mid, int end){
    result crs_result;
    int left_sum = std::numeric_limits<int>::lowest();
    int right_sum = std::numeric_limits<int>::lowest();
    int sum = 0;
    int left, right;

    for(int i = mid; i >= start; i--){
        sum += array[i];
        if(sum > left_sum){
            left_sum = sum;
            left = i;
        }
    }

    sum = 0;
    for(int j = mid + 1; j <= end; j++){
        sum += array[j];
        if(sum > right_sum){
            right_sum = sum;
            right = j;
        }
    }

    crs_result.start = left;
    crs_result.end = right;
    crs_result.sum = left_sum + right_sum;

    return crs_result;
}

测试的main函数如下:

int main()
{
    const int ARRAY_LENGTH = 16;
    int array[ARRAY_LENGTH] = {13, -3, -25, 20, -3, -16, -23, 18, 20, -7, 12, -5, -22, 15, -4, 7};

    result r = max_subarray(array, 0, ARRAY_LENGTH - 1);

    std::cout << "The result is [" << r.start << "-" << r.end << "] = " << r.sum <<std::endl;
}

运行的结果:

The result is [7-10] = 43

总结:最大子数组的问题是分治策略一章中的一个问题。正如文章中提到的:当一个问题足够大,并且需要递归求解时,我们称之为地柜情况,当子问题变得足够小的时候,也就是递归问题触底进入基本情况了。这样的问题除了将问题分解为形式完全一样的规模更小的子问题外,还需要求解与原问题不完全一样的子问题。后面的这个一般看做合并的一部分。归并排序中的子数组合并时候的排序,这里的跨越两个子数组的情况和选择最大的一个子数组都是合并的步骤。

点赞