算法之最大子数组问题

最大子数组问题就是在一个数组中寻找出它的最大的非空连续子数组。本次我将采用3种方式来解决此问题。

方法一:暴力求解方法

暴力方法就是简单的找出出每种可能组合出的组合,找出其中的最大的就可以了。但时间复杂度为n的平方,效率很低。

伪代码:

FIND-MAX-SUBARRAY(A, low, high)
  left = 0
  right = 0
  sum = -∞
  for i = low to high
      current-sum = 0
      for j = i to high
      current-sum += A[j]
      if sum < current-sum
          sum = current-sum
      left = i
      right = j
  return (left, right, sum)

C语言的实现:

int find_max_subArray(int A[], int low, int high)
{
    int left = 0;                 //最大子数组的左边界
    int right = 0;                //最大子数组的右边界
    int sum = MINSUM;             //数组大小
    int current_sum;              //一个用于记录当前数组大小的变量
    int i, j;
    for(i=low;i<=high;i++)
    {
        current_sum = 0;
        for(j=i;j<=high;j++)
        {
            current_sum += A[j];
            if(sum

方法二:分治法分解递归求解

我们要寻找子数组A[low...high]的最大子数组。采用分治法我们就需将子数组分为2个规模尽量相等的子数组。找到子数组的中间位置mid,然后就考虑求解2个子数组A[low...mid]和A[mid+1...high]。此时A[low...high]子数组中的最大连续子数组将是完全存在于A[low...mid]或者A[mid+1...high]或者跨越了重点mid的所有最大子数组中的最大者。我们可以递归的求解A[low...mid]和A[mid+1...high]的最大子数组,因为这两个仍然是最大子数组问题,只是规模更小。因此剩下的全部工作就是寻找跨越中点的最大子数组,然后在三种情况中选取和最大者


伪代码:

FIND-MAX-CROSSING-SUBARRAY(A, low, mid, high)

    left = -∞
    sum = 0
    for i = mid downto low
        sum = sum + A[i]
        if sum > left-sum
            left-sum = sum
            max-left = i
    right-sum = -∞
    sum = 0;
    for j = mid + 1 to high
        sum = sum + A[j]
        if sum >right-sum
            right-sum = sum
            max-right = j
    return (max-left, max-right, left-sum+right-sum)


FIND-MAXIMUM-SUBARRAY(A, low, high)
    if high == low
        return (low, high, A[low])
    else
        mid = (low + high) / 2
        (left-low, left-hight, left-sum) = FIND-MAXIMUM-SUBARRAY(A, low, mid)
        (right-low, right-hight, right-sum) = FIND-MAXIMUM-SUBARRAY(A, mid+1, high)
        (cross-low, cross-hight, cross-sum) = FIND-MAX-CROSSING-SUBARRAY(A, low, mid, high)
        if left-sum >= right-sum and right-sum >= cross-sum
            return (left-low, left-high, left-sum)
        else if right-sum >= left-sum and right-sum >= cross-sum
            return (right-low, right-high, right-sum)
        else 
            return (cross-low, cross-high, cross-sum)

C语言实现:

#include 
#include 

#define MINSUM -1000;


int find_max_crossing_subArray(int A[], int low, int mid, int high);
int find_max_subArray(int A[], int low, int high);

int max_left,max_right;
int left,right;
int main()
{
    int A[] = {1,-1,3,4,6,-5,4, 9};
    printf("max_sum:%d\n", find_max_subArray(A, 0, 7));
    printf("left:%d\nright:%d", left, right);
    return 0;
}


int find_max_crossing_subArray(int A[], int low, int mid, int high)
{
    int left_sum,right_sum;
    left_sum = MINSUM;
    int sum = 0;
    int i;
    for(i = mid; i >=low; i--)
    {
        sum = sum + A[i];
        if(sum > left_sum)
        {
            left_sum = sum;
            max_left = i;
        }
    }
    right_sum = MINSUM;
    sum = 0;
    int j;
    for(j=mid+1; j<=high; j++)
    {
        sum = sum + A[j];
        if(sum > right_sum)
        {
            right_sum = sum;
            max_right = j;
        }
    }
    return left_sum + right_sum;
}

int find_max_subArray(int A[], int low, int high)
{
    int left_sum,right_sum,cross_sum;
    if(low == high)
    {
        left = low;
        right = high;
        return A[low];
    }
    else
    {
        int mid = (low + high) / 2;
        left_sum = find_max_subArray(A, low, mid);
        right_sum = find_max_subArray(A, mid+1, high);
        cross_sum = find_max_crossing_subArray(A, low, mid, high);
        if(left_sum >= right_sum && left_sum >= cross_sum)
        {
            left = low;
            right = mid;
            return left_sum;
        }
        else if(right_sum >= left_sum && right_sum >= cross_sum)
        {
            left = mid + 1;
            right = high;
            return right_sum;
        }
        else
        {
            left = max_left;
            right = max_right;
            return cross_sum;
        }
    }
}

3.线性方法

对于此个问题,有一种达到线性时间的一种算法,如下:

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

C语言代码示例:

#include 
#include 

#define MINSUM -1000;


typedef struct
{
    unsigned left;
    unsigned right;
    int sum;
} max_subarray;

max_subarray find_maximum_subarray(int A[], unsigned low, unsigned high)
{
    max_subarray suffixes[high - low];

    suffixes[0].left = low;
    suffixes[0].right = low + 1;
    suffixes[0].sum = A[low];
    int i;
    for ( i = low + 1; i < high; i++)
    {
        if (suffixes[i - 1].sum < 0)
        {
            suffixes[i].left = i;
            suffixes[i].right = i + 1;
            suffixes[i].sum = A[i];
        }
        else
        {
            max_subarray *previous = &suffixes[i - 1];
            suffixes[i].left = previous->left;
            suffixes[i].right = i + 1;
            suffixes[i].sum = previous->sum + A[i];
        }
    }

    max_subarray *max = &suffixes[0];

    for (i = low + 1; i < high; i++)
    {
        if (max->sum < suffixes[i].sum)
        {
            max = &suffixes[i];
        }
    }

    return *max;
}

int main()
{
    max_subarray ms;
    int A[] = {1,-4,3,4,6,-5,4, 9};
    ms = find_maximum_subarray(A, 0, 7);
    printf("max_sum:%d\n", ms.sum);
    printf("left:%d\nright:%d", ms.left, ms.right);
    return 0;
}


你可能感兴趣的:(算法导论)