最大子数组问题,分治法求解

  最近学习《算法导论》,觉得有些东西真的是很奇妙,分治法竟然如此厉害。

  问题描述:给定一个数组,长度为n,数组中有正数有负数,现在要求找到连续的m个元素组成的子数组,使得这些元素之和是所有子数组中最大的。

乍一看,一般人的解法就会是暴力解法,即从n个数种任意取出两个数,一个作为开头,另一个为结尾,这样花费的时间为O(n2)。但是如果我们使用分治法的思想来考虑问题就会发现,可以用递归来做。我们把数组分成两部分,mid=(low+high)/2;那么这个最大子数组要么在左半个数组内,要么在右半个数组内,要么是一半在左边一半在右边。这样我们可以使用递归的思想,每次把数组分两部分,分别讨论三种情况,那么解决问题的效率就能提高到nlogn的水平。

  下面是我根据原书算法,写的C程序。

#include <stdio.h>

#define MININUM -1000000
typedef struct node{
    int left;
    int right;
    double sum;
}Result;

Result findMaxCrossingSub(double *A,int low,int mid,int high)
{
    double left_sum = MININUM;
    double right_sum= MININUM;
    double sum = 0;
    int max_left;
    int max_right;
    Result result;

    for(int i = mid;i>=low;i--)
    {
        sum = sum + A[i];
        if(sum > left_sum)
        {

            left_sum = sum;
            max_left = i;
        }
    }
    sum = 0;

    for(int j = mid+1 ;j<=high;j++)
    {
        sum = sum + A[j];
        if(sum > right_sum)
        {
            right_sum=sum;
            max_right = j;
        }
    }
    result.left = max_left;
    result.right= max_right;
    result.sum  = left_sum + right_sum;

    return result;
}

Result findMaxSubArray(double *A,int low,int high)
{
    Result result_left,result_right,result_cross;
    if(low == high)
    {
        result_left.left = low;
        result_left.right= high;
        result_left.sum  = A[low];
        return result_left;
    }
    else
    {
        int mid = (low+high)/2;
        result_left = findMaxSubArray(A,low,mid);

        result_right= findMaxSubArray(A,mid+1,high);
        
        result_cross= findMaxCrossingSub(A,low,mid,high);

        if(result_left.sum >= result_right.sum && result_left.sum >= result_cross.sum)
            return result_left;
        else if(result_right.sum >= result_left.sum && result_right.sum >= result_cross.sum)
            return result_right;
        else
            return result_cross;
    }
}

int main()
{
    Result result;
    double A[16]={13,-3,-25,20,-3,-16,-23,18,20,-7,12,-5,-22,15,-4,7};
    
    result = findMaxSubArray(A,0,15);

    printf("最大数组左边界:%d\n右边界:%d\n最大子数组和:%f\n",result.left+1,result.right+1,result.sum);

    return 0;
}


思考:这个问题只是一个基本的模型,在实际生活中有很多利用到它的例子,比如根据股票的跌涨,预测什么时候买进,什么时候抛出。最主要是它教会了我们一个解决问题的方法,以后但凡O(n2)的问题,我们都可以思考一下,我们是否可以做得更好。

原文地址:https://www.cnblogs.com/crossing/p/2951508.html