最大子数组问题——编程珠玑第八章

问题:给定数组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


 

posted @ 2013-08-08 22:08  pangbangb  阅读(409)  评论(0编辑  收藏  举报