最大子数组问题,分治法求解
最近学习《算法导论》,觉得有些东西真的是很奇妙,分治法竟然如此厉害。
问题描述:给定一个数组,长度为n,数组中有正数有负数,现在要求找到连续的m个元素组成的子数组,使得这些元素之和是所有子数组中最大的。
乍一看,一般人的解法就会是暴力解法,即从n个数种任意取出两个数,一个作为开头,另一个为结尾,这样花费的时间为O(n2)。但是如果我们使用分治法的思想来考虑问题就会发现,可以用递归来做。我们把数组分成两部分,mid=(low+high)/2;那么这个最大子数组要么在左半个数组内,要么在右半个数组内,要么是一半在左边一半在右边。这样我们可以使用递归的思想,每次把数组分两部分,分别讨论三种情况,那么解决问题的效率就能提高到nlogn的水平。
下面是我根据原书算法,写的C程序。
#include <stdio.h> #define MININUM -1000000 typedef struct node{ int left; int right; double sum; }Result; Result findMaxCrossingSub(double *A,int low,int mid,int high) { double left_sum = MININUM; double right_sum= MININUM; double sum = 0; int max_left; int max_right; Result result; for(int i = mid;i>=low;i--) { sum = sum + A[i]; if(sum > left_sum) { left_sum = sum; max_left = i; } } sum = 0; for(int j = mid+1 ;j<=high;j++) { sum = sum + A[j]; if(sum > right_sum) { right_sum=sum; max_right = j; } } result.left = max_left; result.right= max_right; result.sum = left_sum + right_sum; return result; } Result findMaxSubArray(double *A,int low,int high) { Result result_left,result_right,result_cross; if(low == high) { result_left.left = low; result_left.right= high; result_left.sum = A[low]; return result_left; } else { int mid = (low+high)/2; result_left = findMaxSubArray(A,low,mid); result_right= findMaxSubArray(A,mid+1,high); result_cross= findMaxCrossingSub(A,low,mid,high); if(result_left.sum >= result_right.sum && result_left.sum >= result_cross.sum) return result_left; else if(result_right.sum >= result_left.sum && result_right.sum >= result_cross.sum) return result_right; else return result_cross; } } int main() { Result result; double A[16]={13,-3,-25,20,-3,-16,-23,18,20,-7,12,-5,-22,15,-4,7}; result = findMaxSubArray(A,0,15); printf("最大数组左边界:%d\n右边界:%d\n最大子数组和:%f\n",result.left+1,result.right+1,result.sum); return 0; }
思考:这个问题只是一个基本的模型,在实际生活中有很多利用到它的例子,比如根据股票的跌涨,预测什么时候买进,什么时候抛出。最主要是它教会了我们一个解决问题的方法,以后但凡O(n2)的问题,我们都可以思考一下,我们是否可以做得更好。