五种求解最大连续子数组的算法

      求解最大连续子数组的内容在《算法导论》这本书上面是作为分治算法的一个例子来进行讲解的,书本上面内容(包括习题)提到了三种解决这一问题的算法,下面是我自己使用C++实现这三种方法的代码和思路放:

一、暴力解法

      对数组内每一个数A[i]进行遍历,然后遍历以它们为起点的子数组,比较各个子数组的大小,找到最大连续子数组

#include "stdafx.h"
//暴力法求最大子数组和问题
int _tmain(int argc, _TCHAR* argv[])
{
    int A[8] = { -6, 10, -5, -3, -7, -1, -1 };
    int array_length = sizeof(A) / sizeof(A[0]);//数组大小
    int sum = -10000;//记录子数组的和
    int low;//记录子数组的底
    int height;//记录子数组的高
    for (int i = 0; i < array_length; i++)
    {
        for (int j = i ; j < array_length; j++)
        {
            int subarraysum=0;//所遍历出来的子数组的和
            //计算遍历的子数组之和
            for (int k = i; k <= j; k++)
            {
                subarraysum += A[k];
            }
            //找出最大的子数组
            if (subarraysum>sum)
            {
                sum = subarraysum;
                low = i;
                height = j;
            }
        }
    }
    printf("%d  %d  %d", low, height,sum);//将结果打印出来
    getchar();
    return 0;
}

可以看到这段程序里面一共嵌套着三层循环,除了最外面的循环会循环n次外内部的循环都比n次小,此程序的时间复杂度为O(n3

二、分治法

     这一算法在《算法导论》当中有很详细的说明,而且上面还有伪代码,我就不啰嗦了,直接放出我的实现代码。

 1 #include "stdafx.h"
 2 //分治法求最大子数组和问题
 3 struct PositioASum {
 4     int low;
 5     int high;
 6     int sum;
 7 };
 8 //寻找包含中点位置的最大子数组函数
 9 PositioASum MaxCrossingSubarray(int a[], int low, int mid, int high)
10 {
11     //求中点左边的最大值和最大位置
12     int maxLeft;//记录左边的最大位置
13     int maxSumLeft=-10000;//记录左边的最大和
14     int sumLeft=0;
15     for (int i = mid; i >= low; i--)
16     {
17         sumLeft += a[i];
18         if (sumLeft > maxSumLeft)
19         {
20             maxSumLeft = sumLeft;
21             maxLeft = i;
22         }
23     }
24     //求中点右边的最大值和最大位置
25     int maxRight=mid+1;//记录右边的最大位置
26     int maxSumRight = -10000;//记录右边的最大和
27     int sumRight = 0;//记录右边子数列的和
28     for (int i = mid+1; i <= high; i++)
29     {
30         sumRight += a[i];
31         if (sumRight > maxSumRight)
32         {
33             maxSumRight = sumRight;
34             maxRight = i;
35         }
36     }
37     PositioASum ps;
38     ps.low = maxLeft;
39     ps.high = maxRight;
40     ps.sum = maxSumLeft + maxSumRight;
41     return ps;
42 }
43 //分治法
44 PositioASum FindMaxSubArray(int a[], int low, int high)
45 {
46     if (low == high)
47     {
48         PositioASum ps;
49         ps.low = low;
50         ps.high = high;
51         ps.sum = a[low];
52         return ps;
53     }
54     else{
55         int mid = (low + high) / 2;
56         PositioASum left = FindMaxSubArray(a, low, mid);
57         PositioASum right = FindMaxSubArray(a, mid + 1, high);
58         PositioASum cross = MaxCrossingSubarray(a, low, mid, high);
59         if (left.sum >= cross.sum && left.sum >= right.sum)
60         {
61             return left;
62         }
63         else if (right.sum >= left.sum && right.sum >= cross.sum)
64         {
65             return right;
66         }else{
67             return cross;
68         }
69     }
70 }
71 
72 int _tmain(int argc, _TCHAR* argv[])
73 {
74     int A[8] = {-1,0,0,0,-1};
75     PositioASum result = FindMaxSubArray(A, 0, 4);
76     printf("%d  %d  %d", result.low, result.high, result.sum);//将结果打印出来
77     getchar();
78     return 0;
79 }

算法的时间复杂度为O(nlogn),由于本程序是在数组的原地址上面进行的,所以总体的控件复杂度为递归的时间复杂度+数组所占的空间为S(n)+S(logn)=S(n)

三、根据书本后面习题所提供的方法

     书本上面提出这样的一种方法:从数组的左边界开始,从左到右处理,记录到目前为止已经处理过的最大子数组。若已知A[1,2,....,j]的最大子数组,则A[1,2,.....,j,j+1]的最大子数组要么是A[1,2,....,j]的最大子数组,要么是某个子数组A[i,....,j+1](1<=i<=j+1)。基于这一思路我的实现过程如下:

 1 #include "stdafx.h"
 2 //基于算法导论上面的习题4.1-5来实现的算法
 3 int _tmain(int argc, _TCHAR* argv[])
 4 {
 5     int A[8] = { -6, 10, -5, 3, 7, -1, -1 };
 6     int array_length = sizeof(A) / sizeof(A[0]);//数组大小
 7     int maxSum = A[0];//记录最大子数组的和
 8     int low=0;//记录最大子数组的底
 9     int high=0;//记录最大子数组的高
10     for (int i = 0; i < array_length-1; i++)
11     {
12         int sum = 0;
13         //寻找以A[i+1]为终点的最大子数组
14         for (int j = i+1; j>=0; j--)
15         {
16             sum += A[j];
17             if (sum>maxSum)
18             {
19                 maxSum = sum;
20                 low = j;
21                 high = i + 1;
22             }
23         }
24     }
25     printf("%d  %d  %d", low, high,maxSum);//将结果打印出来
26     getchar();
27     return 0;
28 }

四、在线算法

//在线法求最大子数组和问题
int _tmain(int argc, _TCHAR* argv[])
{
    int A[8] = { -6, 10, -5, 6, -7, -1, -1 };
    int array_length = sizeof(A) / sizeof(A[0]);//数组大小
    int sum = 0;//记录子数组的和
    int thisSum = 0;
    int low=0;//记录子数组的底
    int height=0;//记录子数组的高
    for (int i = 0; i < array_length; i++)
    {
        thisSum += A[i];
        if (thisSum > sum)
        {
            sum = thisSum;
        }
        else if (thisSum < 0)
        {
            thisSum = 0;
        }
    }
    printf("%d",sum);//将结果打印出来
    getchar();
    return 0;
}

理解这个算法的关键是:使用thisSum来计算当前连续子数组的和,如果thisSum小于0,那么无论后面再加上什么数字都只会让子数组变小,所以抛弃当前子数组,重新开始计算子数组的值。

可以看到这个算法的时间复杂度为O(n)而且控件复杂度为S(n),是解决这一个问题非常有效的一个算法。

五、另外一种稍好的算法

 1 int _tmain(int argc, _TCHAR* argv[])
 2 {
 3     int n;//存储所输入的数组长度
 4     scanf("%d", &n);
 5     int* b = (int*)malloc(n*sizeof(int));//存储所出入的数组
 6     for (int i = 0; i < n; i++)
 7     {
 8         scanf("%d", &b[i]);
 9     }
10     int sum=0;//最大子数列和
11     //寻找最大子数列
12     for (int i = 0; i < n; i++)
13     {
14         int thisSum = 0;//当前数列
15         for (int j = i; j < n; j++)
16         {
17             thisSum += b[j];
18             if (thisSum>sum)
19             {
20                 sum = thisSum;
21             }
22         }
23     }
24     if (sum < 0)
25     {
26         printf("%d",0);
27     }else{
28         printf("%d", sum);
29     }
30     system("pause");
31     return 0;
32 }

     相比于暴力解法每一次都从i到j地重新计算一次,这种算法每一次只需要在原来计算的基础上面加上一个数,所以这种算法少了一层循环,时间复杂度为O(n2)是一种比暴力解法要高效的解法