给定一个数字序列(包含负数),求一个最大的"连续"子序列。最大即子序列各个数值相加和值最大。
NO.1 时间复杂度为O(N^3)的算法
思路:要求一个子序列的和值最大,那么我们要先枚举出所有的子序列,然后分别求他们的和值,利用循环打擂的办法求出最大值输出即可。要求子序列,最简单的方法就是枚举所有的起点和终点,目前为止时间复杂度为O(N^2),之后再求子序列的和值为N,最终的时间复杂度为O(N^3).
//最大连续子序列的超时算法O(N^3)
//先枚举起点和中点,之后再进行O(N)的求和操作,所以为n^3
/*
#include<stdio.h>
#include<string.h>
#define maxn 11111
int number[maxn];
__int64 qiuhe(int s, int e)
{
__int64 sum = 0;
for(int i = s; i <= e; i++)
sum += number[i];
return sum;
}
int main()
{
// freopen("f:\\in.txt", "r", stdin);
int n;
__int64 nowsum, maxsum;
int tou,wei;
int sign;
while(scanf("%d", &n) && n)
{
sign = 0;
maxsum = nowsum = 0;
memset(number, 0, sizeof(number));
for(int i = 0; i < n; i++)
{
scanf("%d", &number[i]);
if(number[i] >= 0)
sign = 1;
}
if(sign == 1)
{
maxsum = number[0];
tou = wei = number[0];
for(int i = 0; i < n; i++)
{
for(int j = i; j < n; j++)
{
nowsum = qiuhe(i,j);
if(maxsum < nowsum)
{
tou = number[i];
wei = number[j];
maxsum = nowsum;
}
}
}
printf("%I64d %d %d\n", maxsum, tou, wei);
}
else
printf("%d %d %d\n", 0, number[0], number[n - 1]);
}
return 0;
}
*/
上述多余的部分只是记录了最大连续子序列的开头和 结尾的元素,不用在意。
NO.2 时间复杂度为O(N^2)的算法
思路:N^2的算法有几种,比如用前n项和相减,被减数最大,减数最小,那么中间子序列的和值肯定是最大的。这里我列出另外一种更容易理解的。即,还是枚举所有的子序列,不同的是,在每一次枚举的过程中顺便求出当前子序列的和值,求出一个之后利用循环打雷进行比较,枚举完成后,最大值自然出来了。
//最大连续子序列超时算法O(N^2)
//枚举起点和终点的过程中,直接算出子序列的和,每一次遍历都能保证最后求的的和是当前最大的,枚举一遍序列之后最大的和值自然出来了
/*
#include<stdio.h>
#include<stdio.h>
#include<string.h>
int number[11111];
int main()
{
//freopen("f:\\in.txt", "r", stdin);
int n;
int sign;
int tou, wei;
__int64 maxsum, nowsum;
while(scanf("%d", &n) && n)
{
sign = 0;
memset(number, 0, sizeof(number));
for(int i =0 ; i < n; i++)
{
scanf("%d", &number[i]);
if(number[i] >= 0)
sign = 1;
}
if(sign == 1)
{
maxsum = nowsum = number[0];
tou = wei = number[0];
for(int i = 0; i < n ; i++)
{
nowsum = 0;
int j;
for(j = i; j < n; j++)
{
nowsum = nowsum + number[j];
if(nowsum > maxsum)
{
tou = number[i];
wei = number[j];
maxsum = nowsum;
}
}
}
printf("%I64d %d %d\n", maxsum, tou, wei);
}
else printf("0 %d %d\n", number[0], number[n - 1]);
}
return 0;
}
*/
NO.3 时间复杂度为O(N * logn)的算法------>分治法
我们都知道一个序列的最大连续子序列可能在以下几个位置:
1.左半边
2.右半边
3.横跨左右两边
//最大连续子序列 o(n * logn)
#include<stdio.h>
#include<stdio.h>
#include<string.h>
int number[11111];
int max3(int i, int j, int k) //递归求三个数最大值//
{
if (i>=j && i>=k)
return i;
return max3(j, k, i);
}
int sum_leftbored(int s, int e)
{
int sum1 = 0;
int maxsum1 = number[e];
for(int i = e; i >= s; i--) //从中间向左求子序列和值//
{
sum1 += number[i];
if(sum1 > maxsum1)
maxsum1 = sum1;
}
return maxsum1;
}
int sum_rightbored(int s1, int e1)
{
int sum2 = 0;
int maxsum2 = number[s1];
for(int j = s1; j <= e1; j++) //从中间向右求子序列的和值//
{
sum2 += number[j];
if(sum2 > maxsum2)
maxsum2 = sum2;
}
return maxsum2;
}
int bitsearch_sum(int left, int right)
{
int leftmaxsum, rightmaxsum;
int leftbordsum, rightbordsum;
int mid;
if(left == right)
return number[left];
mid = (left + right) / 2; //将序列一分为2//
leftmaxsum = bitsearch_sum(left, mid); //求出左半段序列的最大值//
rightmaxsum = bitsearch_sum(mid + 1, right); //求出有半段序列的最大值//
/*
leftbordsum rightboradsum是为了最后求出横跨两个子序列的最大连续子序列及其和值,所以,在求和值的时候,求左半部分要从右向左枚举,求右半部分要
从左向右枚举。因为这样才能够保证横跨两个子序列的最大子序列是连续的,不然颠倒顺序之后,很可能在左半部分的最左边就找到了最大子序列的最大和值,在右半边的最右端找到了最大子序列的最大和值
这样两部分相加求出的最大子序列的和值并不是连续的,是不正确的。分析这种横跨两个子序列求最大连续子序列的特点之后可以看出,所求的最大子序列肯定是从中间的开始往左右延伸的。//
*/
leftbordsum = sum_leftbored(left, mid);
rightbordsum = sum_rightbored(mid + 1, right);
return max3(leftmaxsum, rightmaxsum, (leftbordsum + rightbordsum)); //比较左半边,右半边,横跨两边的最大子序列的最大和值//
}
int main()
{
//freopen("f:\\in.txt", "r", stdin);
int n;
int sign;
int tou, wei;
int maxsum, nowsum;
while(scanf("%d", &n) && n)
{
sign = 0;
memset(number, 0, sizeof(number));
for(int i =0 ; i < n; i++)
scanf("%d", &number[i]);
maxsum = bitsearch_sum(0,n - 1);
printf("%d\n", maxsum);
}
return 0;
}