关于最大连续子序列问题的解法

给定一个数字序列(包含负数),求一个最大的"连续"子序列。最大即子序列各个数值相加和值最大。



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;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值