1.暴力求解法
该方法的思想非常简单,先找出从第1个元素开始的最大子数组,而后再从第2个元素开始找出从第2个元素开始的最大子数组,依次类推,比较得出最大的子数组。实现代码如下:
/*
常规方法,时间复杂度O(n*n)
先从第一个元素开始向后累加,
每次累加后与之前的和比较,保留最大值,
再从第二个元素开始向后累加,以此类推。
*/
int MaxSubSum1(int *arr,int len)
{
int i,j;
int MaxSum = 0;
//每次开始累加的起始位置的循环
for(i=0;i<len;i++)
{
int CurSum = 0;
//向后累加的循环
for(j=i;j<len;j++)
{
CurSum += arr[j];
if(CurSum > MaxSum)
MaxSum = CurSum;
}
}
return MaxSum;
}
显然时间复杂度为O(n*n)。
2.教材分治法
使用分治技术意味着我们要将子数组划分为两个规模尽量相等的子数组。也就是说,找到子数组的中央位置,比如mid,然后考虑求解两个子数组array[low...mid]和array[mid+1...high]。可知array[low...high]的任何连续子数组array[i...j]所处的位置必然是以下三种情况之一:
(1)完全位于子数组array[low...mid]中,因此low<=i<=j<=mid.
(2)完全位于子数组array[mid+1...high]中,因此mid<i<=j<=high.
(3)跨越了中点,因此low<=i<=mid<j<=high.
三种情况中选取和最大者返回即可,代码如下:
#include<iostream>
using namespace std;
int Find_max_crossing_subarray(int array[], int low, int mid, int high)
{
int left_sum = -10000,right_sum=-10000,sum=0,max_left,max_right;
for (int i = mid; i >= low; i--)
{
sum += array[i];
if (sum > left_sum) {
left_sum = sum;
max_left = i; }
}
sum = 0;
for (int j = mid + 1; j <= high; j++)
{
sum += array[j];
if (sum > right_sum)
{
right_sum = sum;
max_right = j;
}
}
return left_sum + right_sum;
}
int Find_max_subarray(int array[], int low, int high)
{
int mid,left_sum,right_sum,cross_sum;
if (high == low)
{
return array[low];
}
else {
mid = (low + high) / 2;
left_sum = Find_max_subarray(array,low,mid);
right_sum = Find_max_subarray(array,mid+1,high);
cross_sum = Find_max_crossing_subarray(array,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;
}
void main()
{
int array[] = { 1,-2,3,10,-4,7,2,-19,2 };
int sum = Find_max_subarray(array,0,8);
cout <<"最大子数组的和为:"<<sum << endl;
system("pause");
}
通过主方法求解算法递归式得到时间复杂度为O(nlog(n)).
3.线性复杂度方法
该算法在每次元素累加和小于0时,从下一个元素重新开始累加。代码如下:
#include<iostream>
#include<stdlib.h>
using namespace std;
//求最大连续子序列和
int FindGreatestSumOfSubArray(int arry[],int len)
{
if(arry==NULL||len<=0)
return -1;
int start=0,end=0;//用于存储最大子序列的起点和终点
int p=0;//指针,用于遍历数组。
int currSum=0;//保存当前最大和
int greatestSum=-10000;//保存全局最大和
for(int i=0;i<len;i++)
{
if(currSum<0)//如果当前最大和为负数,则舍弃前面的负数最大和,从下一个数开始计算
{
currSum=arry[i];
p=i;
}
else
currSum+=arry[i];//如果当前最大和不为负数则加上当前数
if(currSum>greatestSum)//如果当前最大和大于全局最大和,则修改全局最大和
{
greatestSum=currSum;
start=p;
end=i;
}
}
cout<<"最大子序列位置:"<<start<<"--"<<end<<endl;
return greatestSum;
}
void main()
{
//int arry[]={1,-2,3,10,-4,7,2,-5};
int arry[]={1,-2,3,10,-4,7,2,-19,2};
int len=sizeof(arry)/sizeof(int);
//cout<<len<<endl;
int sum= FindGreatestSumOfSubArray(arry,len);
cout<<"最大子序列和:"<<sum<<endl;
system("pause");
}
可见时间复杂度为O(n),其中,
(1)p为一个遍历用于保存遍历数组中发现的最大和的起始,原来的start和end只用于保存真是的开始于结尾
(2)当currSum<0的时候,我们只让p值为最大和子数组的开始
(3)在最后判断currSum>greatestSum的时候,只有当currSum>greatestSum成立,才让start=p,否则就表明以p开头的子数组最大和不是最大的。