zoukankan      html  css  js  c++  java
  • 最大子数组问题——编程珠玑第八章

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


  • 相关阅读:
    C语言中do...while(0)用法小结
    C语言函数指针的用法
    C语言预处理命令之条件编译
    欧拉计划11-15题
    欧拉计划6-10题
    已加载“C:WindowsSysWOW64 tdll.dll”。无法查找或打开 PDB 文件。
    C++使用SQLite步骤及示例
    linux 安装sysstat使用iostat、mpstat、sar、sa
    Nmon命令行:Linux系统性能的监测利器
    linux服务器性能检测工具nmon使用
  • 原文地址:https://www.cnblogs.com/pangblog/p/3246993.html
Copyright © 2011-2022 走看看