01-复杂度1 最大子列和问题 (20分)
数据结构(浙江大学)
给定K个整数组成的序列{ N
1
, N
2
, …, N
K
},“连续子列”被定义为{ N
i
, N
i+1
, …, N
j
},其中 1≤i≤j≤K。“最大子列和”则被定义为所有连续子列元素的和中最大者。例如给定序列{ -2, 11, -4, 13, -5, -2 },其连续子列{ 11, -4, 13 }有最大的和20。现要求你编写程序,计算给定整数序列的最大子列和。
本题旨在测试各种不同的算法在各种数据情况下的表现。各组测试数据特点如下:
数据1:与样例等价,测试基本正确性;
数据2:102个随机整数;
数据3:103个随机整数;
数据4:104个随机整数;
数据5:105个随机整数;
输入格式:
输入第1行给出正整数K (≤100000);第2行给出K个整数,其间以空格分隔。
输出格式:
在一行中输出最大子列和。如果序列中所有整数皆为负数,则输出0。
输入样例:
6
-2 11 -4 13 -5 -2
输出样例:
20
关于这道题目,我们可以用四种方法来算。
四种方法,同时也是四种不同的时间复杂度,让我们来深入了解一下。
第一种是这样的:
#include<stdio.h>
#include<stdlib.h>
#include<math.h>
#include<time.h>
#define N 100000
int MaxSubseqSum1( int A[], int n );
int main()
{
int i,x;
int a[N]={0};
scanf("%d",&x);
for(i=0;i<x;i++)
scanf("%d",&a[i]);
printf("%d",MaxSubseqSum1(a,x));
return 0;
}
int MaxSubseqSum1( int A[], int n )
{ int ThisSum, MaxSum = 0;
int i, j, k;
for( i = 0; i < n; i++ ) { /* i是子列左端位置 */
for( j = i; j < n; j++ ) { /* j是子列右端位置 */
ThisSum = 0; /* ThisSum是从A[i]到A[j]的子列和 */
for( k = i; k <= j; k++ )
ThisSum += A[k];
if( ThisSum > MaxSum ) /* 如果刚得到的这个子列和更大 */
MaxSum = ThisSum; /* 则更新结果 */
} /* j循环结束 */
} /* i循环结束 */
return MaxSum;
}
那么我们来看一下它的运算结果:
用我们学到的时间复杂度来算的话就是O(n^3)
可想而知,当数为10000或者100000的时候,O(n^3)有多么恐怖了!
那我们现在用第二种方法:
int MaxSubseqSum2( int A[], int n )
{ int ThisSum, MaxSum = 0;
int i, j, k;
for(i=0;i<n;i++)
{
ThisSum=0;//每次更新都要讲ThisSum置于0
for(j=i;j<n;j++)
{
ThisSum+=A[j];
if(ThisSum>MaxSum)
{
MaxSum=ThisSum;//MaxSum永远存着最大的值
}
}
}
return MaxSum;
}
那么这个算法的运行结果呢??
如图:
那么这段代码是可以跑的,它的时间复杂度为O(n^2).
虽然代码跑成功了,但是最后一个跑的时间还是很长,有没有更好的解决办法呢!
根据陈越姥姥的方法就是分而治之.
分而治之是什么呢?
在数组中,它的数是不连续的,即正负号不确定,大小不确定,所以分而治之的方法就是:
先确定数组的中间,然后对左区间进行递归算法,算出左区间的最大子列和,然后再递归算出右区间的最大子列和。但还有可能出现跨越中间的最大子列和,所以我们需要算出从中间到左区间以及从中间到右区间的最大子列和,并把它们加在一起。这样我们就有三段最大值,对它们进行比较,返回最大的那个值。
那我们用代码来看看到底时间复杂度是不是更加简洁了呢?
int Max(int a,int b,int c)
{
a=(a>b? a:b);
return (a>c? a:c);
}
int MaxSubseqSum3(int A[],int left,int right)
{
if(left==right)
{
return (A[left]>0? A[left]:0);
}
else if(left<right)
{
int mid=(left+right)/2;
int lsum=0,rsum=0,msum=0;
lsum=MaxSubseqSum3(A,left,mid);//递归求左区间的最大子列和
rsum=MaxSubseqSum3(A,mid+1,right);//递归求右区间的最大子列和
msum=0;
int thisMSum=msum;
/*求跨界最大子列和*/
for(int i=mid;i>=left;i--)
{
thisMSum+=A[i];
if(thisMSum>msum)
{
msum=thisMSum;
}
}
for(int i=mid+1;i<=right;i++)
{
thisMSum+=A[i];
if(thisMSum>msum)
{
msum=thisMSum;
}
}
return Max(lsum,msum,rsum);
}
我们再看一下时间复杂度如何:
运行时间大大降低了呀!我们来算一下时间复杂度:
在进行左区间运算的时候
只有一半的数,我们可以记为T(n/2),同时,右边也是,也是T(n/2)。在跨越边界求最大子列和的时候,我们可以认为时间复杂度是n的常数倍。所以总的时间负责度是T(n)=2T(n/2)+cn.
c为常数.
那么T(n/2)=?
可以得知T(n/2)=2[2T(n/2^ 2)+cn/2]+cN=4T(n/2^2)+2cn
慢慢展开,最终n/2^k接近1,近似等于1
最后结果等于 T(N)=2^ kO(1)+ckn 又因为n/2^ K=1,所以n=2^k.可以化简为 nO(1)+c log n
所以时间复杂度是O(n*logn)
那么有没有时间复杂度更低的呢?还是有的!
最后一种方法:在线处理
我们来看一下代码:
int MaxSubseqSum4(int A[],int n)
{
int ThisSum=0,MaxSum=0;
int i;
for(i=0;i<n;i++)
{
ThisSum+=A[i];
if(MaxSum<ThisSum)
MaxSum=ThisSum;
else if(ThisSum<0)//等于负数时,前面的数肯定不是最大子列和了,再从当前进行求最大值.
ThisSum=0;
}
return MaxSum;
}
跑一下代码:
此时的时间复杂度就是T(N)O(N)=了.
好了,以上就是所有方法以及代码的描述,希望大家能够用心体会,先了解再去抄!或者尝试自己写!