什么是最大子数组问题呢?
给出一列数组,
假设张三有超能力,能预测股票走向
该数组为一个股票的价格走向;第一行是日期,第二行为该日期股票的大盘价格
求这16天哪一天买入,哪一天卖出,赚的最多?
本问题在这里学习两种解法:
第一种是暴力算法
把所有的组合一一列出并且求出大小进行存储,然后比较选出最大的,即可解的答案。
从这个不难分析得出,假设有n天,其有(2的n次方)种组合
其时间复杂度为O(n²)。
因此我们要对其进行优化,采用分治的思路进行设计:
第二种方法:
我们把子数组分布分析一下:
假设首元素为low ,顶元素为high,中间为mid;
假设有一中心点,子数组可能分布的情况为三种:
- 全部在左边 A (low , mid)(low≤ i ≤ j ≤high)
- 全部在右边A( mid + 1 , high) (mid < i ≤ j ≤high)
- 跨越中心点 (low ≤ i ≤ mid < j ≤high)
如图
所以我们可以写出策略:
先定义一个结构体:
typedef struct {
int low,high,sum;
}part;
代码实现如下:
int mid;
mid = (low+high)/2;
p = find_maximum_subarray(a,low,mid);
left_low = p.low;
left_high = p.high;
left_sum = p.sum;
p = find_maximum_subarray(a,mid+1,high);
right_low = p.low;
right_high = p.high;
right_sum = p.sum;
p = find_crossing_subarray(a,low,mid,high);
cross_low = p.low;
cross_high = p.high;
cross_sum = p.sum;
这个代码的流程就是将数组不断的平分然后分成一个个小的“中点”
然后对这些“中点”进行跨中点的比较
代码实现如下:
part find_crossing_subarray(int a[],int low,int mid,int high){
part p;
int i,j;
int right_sum;
int max_left,max_right;
int left_sum = -1000; //此处为负无穷,要小于所有的值,才能作为初始值比较
int sum = 0;
for(i = mid;i>=low;i--){ //从中点到最左侧依次递减
sum += a[i];
if(sum>left_sum){
left_sum = sum;
max_left = i;
}
}
sum = 0;
for(j = mid+1;j<=high;j++){
sum += a[j];
if(sum>right_sum){
right_sum = sum;
max_right = j;
}
}
p.high = max_right;
p.low = max_left;
p.sum = right_sum + left_sum;
return p;
}
然后进行左侧、右侧、跨中点三个值的比较,返回最大值
if(left_sum>=right_sum&&left_sum>=cross_sum){
p.low = left_low;
p.high = left_high;
p.sum = left_sum;
return p;
}
else if(right_sum>=left_sum&&right_sum>=cross_sum){
p.high = right_high;
p.low = right_low;
p.sum = right_sum;
return p;
}
else{
p.high = cross_high;
p.low = cross_low;
p.sum = cross_sum;
return p;
}
完整代码实现如下:
#include <stdio.h>
#include <stdlib.h>
typedef struct {
int low,high,sum;
}part;
part find_crossing_subarray(int a[],int low,int mid,int high){
part p;
int i,j;
int right_sum;
int max_left,max_right;
int left_sum = -1000; //此处为负无穷,要小于所有的值,才能作为初始值比较
int sum = 0;
for(i = mid;i>=low;i--){ //从中点到最左侧依次递减
sum += a[i];
if(sum>left_sum){
left_sum = sum;
max_left = i;
}
}
sum = 0;
for(j = mid+1;j<=high;j++){
sum += a[j];
if(sum>right_sum){
right_sum = sum;
max_right = j;
}
}
p.high = max_right;
p.low = max_left;
p.sum = right_sum + left_sum;
return p;
}
part find_maximum_subarray(int a[],int low,int high){
part p;
int left_high,left_low,left_sum;
int right_low,right_high,right_sum;
int cross_low,cross_high,cross_sum;
if(high==low){
p.low = low;
p.high = high;
p.sum = a[low];
return p;
}
else{
int mid;
mid = (low+high)/2;
p = find_maximum_subarray(a,low,mid);
left_low = p.low;
left_high = p.high;
left_sum = p.sum;
p = find_maximum_subarray(a,mid+1,high);
right_low = p.low;
right_high = p.high;
right_sum = p.sum;
p = find_crossing_subarray(a,low,mid,high);
cross_low = p.low;
cross_high = p.high;
cross_sum = p.sum;
if(left_sum>=right_sum&&left_sum>=cross_sum){
p.low = left_low;
p.high = left_high;
p.sum = left_sum;
return p;
}
else if(right_sum>=left_sum&&right_sum>=cross_sum){
p.high = right_high;
p.low = right_low;
p.sum = right_sum;
return p;
}
else{
p.high = cross_high;
p.low = cross_low;
p.sum = cross_sum;
return p;
}
}
}
int main(){
int a[16] = {13,-3,-25,20,-3,-16,-23,18,20,-7,12,-5,-22,15,-4,7};
part p;
p = find_maximum_subarray(a,0,15);
printf("%d,%d,%d",p.low+1,p.high+1,p.sum);
}