最大子数组问题——编程珠玑第八章
问题:给定数组a[n],求其子数组的最大和。例如输入数组为:
1,-2,3,10,-4,7,2,-5
和最大的子数组为3,10,-4,7,2,因此输出为18.
没有想到更“巧妙”的算法前,用所谓“暴力”算法一般都能解决:
1、简单算法:
求出每个子数组的和,记录最大的那个即可。
void maxSum_1(int a[], int n) { int sum=NM,m=NM;//NM是一个很小的负数,例如-99999999.下同 for(int i=0; i<n; i++) { sum=0; for(int j=i; j<n; j++) { sum += a[j]; m = max(m,sum); } } cout<<__FUNCTION__<<" : "<<m<<endl; }
算法复杂度是O(n2).书中的算法还有一个O(n3)的,比上面这个复杂之处是求sum时再从头开始,此处就不再提了。
2、分治法
把数组分成均等的两段l和u,那么原问题的解就是:最大子数组要么在l中,要么在u中,要么横跨l和u。
横跨中点时的情况比较难理解:这时左面不再是求最大子数组,而是从中间向两边求最大和。
int maxSum_2_helper(int a[], int l, int u) { if(l>u) return NM; //非法参数 if(l==u) return a[l]; //只有一个元素 int m = (l+u)/2 ; ///求左面一半的连续最大和 int lmax=NM; for(int i=m,s=0; i>=l; i--)///从中点到最左端 { s += a[i]; lmax = max(lmax,s); } ///求右面一半的连续最大和 int rmax=NM; for(int i=m+1,s=0; i<=u; i++) { s += a[i]; rmax = max(rmax,s); } ///合并结果 ///rlm是左右子数组中的最大子数组的和 int rlm = max(maxSum_2_helper(a,l,m), maxSum_2_helper(a,m+1,u)) ; return max(rlm,lmax+rmax);///合并跨越中点的和两边的最大和 } void maxSum_2(int a[], int n) { int m=maxSum_2_helper(a,0,n-1); cout<<__FUNCTION__<<" : "<< m <<endl; }
该算法非常微妙,其复杂度是O(nlogn).
3、巧妙的扫描算法
考虑如果我们已经求得a[0…i-1]的最大子数组和为m,那么a[0…i]的最大子数组要么在0~i-1中(存储在maxSoFar),要么截止到i(存储在maxEndingHere)。
void maxSum_3(int a[], int n) { int maxSoFar=NM, maxEndingHere=NM; for(int i=0; i<n; i++) { maxEndingHere = max(maxEndingHere+a[i], a[i]); maxSoFar = max(maxSoFar,maxEndingHere); } cout<<__FUNCTION__<<" : "<< maxSoFar <<endl; }
这个算法的另一种形式:
void maxSum_3_2(int a[], int n) { int m=NM, sum=0; for(int i=0; i<n; i++) { sum += a[i]; m = max(m,sum); if(sum<0) sum = 0; } cout<<__FUNCTION__<<" : "<< m <<endl; }
相关问题:
a)查找总和最接近0的连续子数组,更一般的情况,查找最接近给定实数t的连续子数组。
如果a[i…j]最接近0,那么a[0…i]的和就与a[0…j]的和最接近。所以使用累加数组即可。由原数组构造新数组s[n](或者直接在原数组上操作),使s[i]=sum(a[0…i])。然后对s[n]排序,求出s[n]中最接近的两个数。这两个数的差值即为最接近0的子数组和。需要说明的是,这时需要新建数据结构,记录原来数组中每个元素的位置。
由于排序最快为O(nlogn),因此这个时间复杂度O(nlogn),空间O(n).
代码如下,使用stl的排序函数sort。
struct AP { int value; int pos; }; bool operator<(const AP & n, const AP &m) { return n.value<m.value; } template <typename T> inline T abs(T n) { return n<0?(-n):n; } int nearZero(int a[], int n) { AP *b=new AP[n](); int sum=0; for(int i=0;i<n;i++) { b[i].pos = i; sum += a[i]; b[i].value = sum; } sort(b,b+n); ///寻找差绝对值最小的两个元素 int dmin=NM; int t = 0; for(int i=1; i<n; i++) { if( abs(dmin) > abs(b[i].value - b[i-1].value)) { dmin = abs(b[i].value - b[i-1].value); t = i; } } ///t 和 t-1 位置元素之差极为最接近0 int r=NM; int s,e;///s为子数组起始下表,e为子数组末尾下表 if(b[t].pos<b[t-1].pos) { r = b[t-1].value - b[t].value; s = b[t].pos + 1; e = b[t-1].pos; } else { r = b[t].value - b[t-1].value; s = b[t-1].pos + 1; e = b[t].pos; } cout<<__FUNCTION__<<" : "<< r <<endl; cout<<"["<<s<<","<<e<<"]"<<endl; delete [] b; return r; }
对于任意实数的问题,目前还没想到合适的解法。开始觉得对数组中每个元素平移是一个貌似可行的解决方法,但是仔细分析后发现是不行的。
后序还有最大子矩阵问题。
未完待续。
参考:http://www.cnblogs.com/wuyuegb2312/p/3139925.html