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

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


 

你可能感兴趣的:(编程珠玑)