最大连续子数组和问题:任意给定一个int型数组(里面的元素有正有负),要求找出它的所有的连续子数组中元素之和最大的一个,并指出这个子数组的元素之和以及左右脚标。
1、暴力求解
有一种最容易想到的不计代价的方式:找出所有的子数组,并求出每一个子数组元素的和,找出所有的和中最大的,最大和对应的子数组就是我们要找的。
<span style="font-size:14px;">#include <stdio.h>
#define L 20 //数组的长度
struct result{ //用于保存最大子数组的结构
int left;
int right;
int sum;
} subarray;
struct result max_subarray(int a[]) {
int i, j;
int left;
int right;
int max_sum = a[0];
for(i = 0; i < L; i++) {
int sum = 0;
for(j = i; j < L; j++){
sum = sum + a[j];
if(sum >= max_sum){
max_sum = sum;
left = i;
right = j;
}
}
}
subarray.left = left;
subarray.right = right;
subarray.sum = max_sum;
return subarray;
}
int main(void) {
int a[L] = {-3, 2, -1, 5, -2, -4, 3, 1, -2, 6, 0, 1, -1, -2, 2, 3, -4, 2, 1, -10};
struct result res;
res = max_subarray(a);
printf("left = %d, right = %d, sum = %d\n", res.left, res.right, res.sum);
return 0;
}
</span>
2、分治策略
使用分治策略意味着要将原问题划分成两个规模为原来的一半的子问题,也就是说要将原数组a[low...high]划分成两个子数组a[low...mid]和a[mid+1...high]。原数组的所有的子数组必定处于下面三种情况之一:
(1)完全位于子数组a[low...mid]之间,即:low ≤ i ≤ j ≤mid;
(2)完全位于子数组a[mid+1...high]之间,即:mid+1 ≤ i ≤ j ≤ high;
(3)跨越了中点,即:low ≤ i ≤ mid ≤ j ≤ high;
对于(1)(2)两种情况,可以采用分治策略,继续划分,直到子数组的个数为1为止,此时该子数组的最大子数组就是他本身。对于第三种情况,只需要找到以a[mid]为最后元素的最大子数组和以a[mid+1]为首元素的最大子数组,将这两个子数组连在一起,就得到了跨越中点的最大子数组。
一种可行的代码:
<span style="font-size:14px;">#include <stdio.h>
#include <math.h>
struct result {
int left;
int right;
int sum;
} cross_result, left_result, right_result, end_result;
//找出跨越中点的所有子数组中元素的和最大的一个
struct result find_max_corssing_subarray(int a[], int low, int mid, int high) {
int left_sum = a[mid];//用于保存目前为止找到的最大和
int right_sum = a[mid+1];
int max_left, max_right;
int sum = 0;
int i, j;
//找到左半部分的最大子数组
for(i = mid; i >= low; i--) {
sum = sum + a[i];
if(sum > left_sum) {
left_sum = sum;
max_left = i;//记录当前的下标
}
}
//找到右半部分的最大子数组
sum = 0;
for(j = mid+1; j <= high; j++) {
sum = sum + a[j];
if(sum > right_sum){
right_sum = sum;
max_right = j;
}
}
cross_result.left = max_left;
cross_result.right = max_right;
cross_result.sum = left_sum + right_sum;
return cross_result;
}
struct result find_maximum_subarray(int a[], int low, int high) {
int mid;
if(high == low){
end_result.left = end_result.right = low;
end_result.sum = a[low];
return end_result;
}
else {
mid = (int)floor((low + high) / 2); //floor向下取整返回的是double类型
left_result = find_maximum_subarray(a, low, mid);
right_result = find_maximum_subarray(a, mid + 1, high);
cross_result = find_max_corssing_subarray(a, low, mid, high);
if(left_result.sum >= right_result.sum && left_result.sum >= cross_result.sum)
return left_result;
else if(right_result.sum >= left_result.sum && right_result.sum >= cross_result.sum)
return right_result;
return cross_result;
}
}
int main(void) {
int a[] = {-3, 2, -1, 5, -2, -4, 3, 1, -2, 6, 0, 1, -1, -2, 2, 3, -4, 2, 1, -10};//20个
struct result res;
res = find_maximum_subarray(a, 0, 19);
printf("left = %d right = %d sum = %d\n", res.left, res.right, res.sum);
return 0;
}</span>
复杂度分析:前者很容易看出T(n) = Ɵ(nlgn)。对于后者花费的时间T(n),找到跨越中点的最大子数组花费时间Ɵ(n),找到左边和右边的最大子数组花费的时间都为T(n/2),所以T(n) = Ɵ(n) + 2T(n/2),也即T(n) = Ɵ(nlgn)。