最大子数组 三种不同复杂度的算法

本章的内容主要讨论以及实现解决最大子数组的三种不同复杂度的解法

最大子数组问题在生活中有很多应用,本章讨论的问题的背景来源于《算法导论》第四章 4.1最大子数组问题。

注意,我们求的是一个最大子数组,因为最大子数组可能有很多个。求出的是该算法算出的第一个最大子数组。


1、暴力求解 复杂度为Ω(n^2)

对于一个给定的数组,暴力地尝试求和每一个子数组,由于n个数共有n(n-1)/2种组合,而n(n-1)/2=Θ(n^2)。处理每一种组合所花费的时间至少也是常量,所以复杂度为Ω(n^2)。

下面给出代码

#include<cstdio>
int main(void)
{
    int i,j,length,first,last;
    int sum=0,max=-10000;
    int a[1000];
    printf("please enter the length of your array\n");
    scanf("%d",&length);
    printf("Please enter your array\n");
    for(i=0;i<length;i++)
    {
        scanf("%d",&a[i]);
    }
    for(i=0;i<length;i++)
    {
        sum=0;
        for(j=i;j<length;j++)
        {
            sum=sum+a[j];
            if(sum>max)
            {
                max=sum;
                first=i+1;
                last=j+1;
            }
        }
    }
    printf("the sum of the maximun array is %d, from %dth to %dth.\n",max,first,last);
    return 0;
}


2、分治求解 复杂度为Θ(n lgn)

由于篇幅有限,我们仅对求解方法的思路和实现进行说明,而对复杂度为Θ(n lgn)的证明,还请读者自行完成。

假定有一个数组A[low...high],要用分治策略求解,意味着我们要把数组划分成规模尽量相等的两个子数组,不妨令中间元素为mid。那么两个子数组为A[low...mid],A[mid+1...high],而对数组A[low...high],它的任何连续子数组A[i...j]必定是以下三种情况之一:

· 完全位于子数组A[low...mid]中,因此low<=i<=j<=mid;

· 完全位于子数组A[mid+1...high],因此mid+1<=i<=j<=high;

· 跨越了中点,因此low<=i<=mid<j<=high;

所以最大子数组必然处于以上三种情况之一,既然如此,我们可以分别求出以上三种情况的全部子数组的和的最大者,则最大子数组为三种情况的最大者。对于前两种情况,它们的问题仍然是最大子数组问题,可以递归地求解,重点在于求第三种情况的最大子数组。

对于第三种情况,由于加入了限制——求出的子数组必须跨越中点mid,我们可以在线性时间内求出跨越中点的最大子数组。任何跨越mid的子数组都由两个子数组A[i...mid]和A[mid+1...j]组成,所以我们只需要从中点mid开始,分别向两边求和,求出形如A[i...mid]和A[mid+1...high]的最大子数组,然后将得到的两个最大子数组合并即可。

下面给出代码

#include<cstdio>
int FIND_MAXIMUM_SUBARRAY( );
int FIND_MAX_CROSSING_SUBARRAY( );
int main( )
{
    int i,length,a[1000],sum;
    printf("please enter the length of your array\n");
    scanf("%d",&length);
    printf("Please enter your array\n");
    for(i=0;i<length;i++)
    {
        scanf("%d",&a[i]);
    }
    sum=FIND_MAXIMUM_SUBARRAY(a,0,length-1);
    printf("the sum of the maximun subarray is %d.\n",sum);
    return 0;
}
int FIND_MAX_CROSSING_SUBARRAY(int a[],int low,int mid,int high)
{
    int i;
    int left_sum=-10000;
    int right_sum=-10000;
    int sum=0;
    for(i=mid;i>=low;i--)
    {
        sum=sum+a[i];
        if(sum>left_sum)
        {
            left_sum=sum;
        }
    }
    sum=0;
    for(i=mid+1;i<=high;i++)
    {
        sum=sum+a[i];
        if(sum>right_sum)
        {
            right_sum=sum;
        }
    }
    return left_sum+right_sum;
}
int FIND_MAXIMUM_SUBARRAY(int a[],int low,int high)
{
    int mid=(low+high)/2;
    int left_sum,right_sum,cross_sum;
    if(high==low)
    {
        return a[low];
    }
    else
    {
        left_sum=FIND_MAXIMUM_SUBARRAY(a,low,mid);
        right_sum=FIND_MAXIMUM_SUBARRAY(a,mid+1,high);
        cross_sum=FIND_MAX_CROSSING_SUBARRAY(a,low,mid,high);
        if(left_sum>=right_sum&&left_sum>=cross_sum)
        {
            return left_sum;
        }
        else if(right_sum>=left_sum&&right_sum>=cross_sum)
        {
            return right_sum;
        }
        else
        {
            return cross_sum;
        }
    }
}


3、线性算法 复杂度为Θ(n)

本章的主角,因为前面两种都可以用普通的算法得出,而这个线性算法,需要一点点脑筋才能想到。

算法正确性的证明留给读者思考,这里重点描述该算法的思路。

《算法导论》4.1-5给出的思想是这样的:从数组的左边界开始,由左至右处理,记录到目前为止已经处理过的最大子数组。若已知A[1...j]的最大子数组,基于如下性质将解扩展为A[1...j+1]的最大字数组:A[1...j+1]的最大子数组要么是A[1...j]的最大子数组,要么是某个子数组A[1...j+1](1<=i<=j+1)。在已知A[1...j]的最大子数组的情况下,可以在线性时间内找出形如A[i...j+1]的最大子数组。

我用另外一种更易懂的来表达这种思路,给定数组A[1...j],从A[1]到A[j]遍历该数组,A[0]为最大子数组,sum为当前遍历子数组的和,max为当前遍历的最大子数组的和。那么遍历到A[1]的时候,sum=sum+A[0],此时有三种可能:1、A[1]比sum要大,那就是说sum是负数,当前遍历的子数组的和为负,可是最大子数组不可能由一个负数再加另外一个数组组成,所以就把sum重置为A[1],即当前的元素,那么当前遍历的子数组就由A[1]开始重新遍历;2、A[1]不比sum要大,那就继续遍历3、sum比max要大,那就意味着,当前遍历的子数组要比之前遍历到的最大子数组要大,那就把sum的值赋值给max,当前遍历的子数组为最大子数组;

下面给出代码

#include<cstdio>
int main(void)
{
    int i,length;
    int sum,max,first,last,temp;
    int a[1000];
    printf("please enter the length of your array\n");
    scanf("%d",&length);
    printf("please enter your array\n");
    for(i=0;i<length;i++)
    {
        scanf("%d",&a[i]);
    }
    max=a[0];
    sum=a[0];
    first=1;
    last=1;
    temp=1;
    for(i=0;i<length-1;i++)
    {
        if(a[i+1]>sum+a[i+1])
        {
            temp=i+2;
            sum=a[i+1];
        }
        else
        {
            sum=sum+a[i+1];
        }
        if(sum>max)
        {
            first=temp;
            last=i+2;
            max=sum;
        }
    }
    printf("the sum of the maximun array is %d, from %dth to %dth.\n",max,first,last);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/fordreamyue/article/details/51714404
今日推荐