`
anna_zr
  • 浏览: 200500 次
  • 性别: Icon_minigender_2
  • 来自: 北京
社区版块
存档分类
最新评论

最大子序列和

阅读更多
问题描述:
有一串数字(可正可负的int,放在数组Num里),要求找到起始位置start和终止位置end,使得从start位置到end位置的所有数字之和最大,返回这个最大值max。最简单的方法是用动态规划算法实现:设 f[x] 为以 a[x] 终止且包含 a[x] 的最大序列的和,有:
   f[1] = a[1];

以a[x]为结尾的所有序列段可以表示成a[i],a[i+1],....,a[x-1],a[x],(1<=i<=x);

其序列段和sum1=a[i]+a[i+1]+....+a[x-1]+a[x];

由于以a[x+1]结尾的所有序列段中,除了是长度为1的a[x+1]之外,其它所有的序列段a[x+1]前面都那部分

都可以由以a[x]结尾的所有序列段集合构造而成;

那么以a[x+1]为结尾的所有序列段可以对应的表示sum2=a[i]+a[i+1]+....+a[x-1]+a[x]+a[x+1]=sum1+a[x+1]或者

sum2=a[x+1],那么由sum1和sum2的对应关系若某个对应以a[x]为的序列段的sum1在所有序列段的和sum1中最大,那么这个序列段对应的以a[x+1]为尾的序列段(长度大于2),其序列段也必在其对应的以a[x+1]为尾的所有长度大于2的序列段中其序列段和sum2最大;

即sum1是最大那么sum2必也是最大(在长度大于2的序列段中),那么有sum2=sum1+a[x+1]的到的sum2和a[x+1]比较即可;

显然f[x]<0,那么必然有f[x]+a[x+1]<a[x+1],否则f[x]+a[x+1]>=a[x+1];

所以f[x+1]=(f[x]<0)?(a[x+1])?(f[x]+a[x+1]);

     f[1]=a[1];

那么最大子序列的和就是 f[1] .. f[n] 中最大的一个。


一般 maxSubSequenceSum0  O(n^3)

简单优化过的算法 maxSubSequenceSum1  O(n^2)

分治法优化的算法 maxSubSequenceSum2  O(n*log(n))

动态规划的算法 maxSubSequenceSum3  O(n)


#include <math.h>

#include "mymath.h"

/*
* 计算序列的某段子序列的和,maxSubSequenceSum0使用
*/
static int subSequenceSum(int a[], int left, int right)
{
    int i, sum = 0;
    for (i = left; i <= right; i++)
    {
        sum = sum + a[i];
    }
    return sum;
}

/*
* 三层遍历求子序列和的最大值,算法复杂度O(n^3)
*/
int maxSubSequenceSum0(int a[], int len)
{
    int i, j;
    int curSum; /* 当前序列和 */
    int maxSum; /* 最大序列和 */

    /* 初始化最大子序列和为序列第一个元素 */
    maxSum = a[0];

    /* 第一层循环定义子序列起始位置 */
    for (i = 0; i < len; i++)
    {
        /* 起始位置为i,初始化当前和为0 */
        curSum = 0;

        /* 第二层循环定义子序列结束位置 */
        for (j = i; j < len; j++)
        {
            /* 第三层循环在函数sumSubseqence中,计算子序列和 */
            curSum = subSequenceSum(a, i, j);

            /* 与最大子序列和比较,更新最大子序列和 */
            if (curSum > maxSum)
            {
                maxSum = curSum;
            }
        }
    }
    return maxSum;
}

/*
* 双层遍历求子序列和的最大值,算法复杂度O(n^2)
*/
int maxSubSequenceSum1(int a[], int len)
{
    int i, j;
    int curSum; /* 当前序列和 */
    int maxSum; /* 最大序列和 */

    /* 初始化最大子序列和为序列第一个元素 */
    maxSum = a[0];

    /* 外层循环定义子序列起始位置 */
    for (i = 0; i < len; i++)
    {
        /* 起始位置为i,初始化当前和为0 */
        curSum = 0;

        /* 内层循环定义子序列结束位置 */
        for (j = i; j < len; j++)
        {
            /* 计算子序列和,并与最大子序列和比较,更新最大子序列和 */
            curSum = curSum + a[j];

            /* 与最大子序列和比较,更新最大子序列和 */
            if (curSum > maxSum)
            {
                maxSum = curSum;
            }
        }
    }
    return maxSum;
}

/*
* 某段字序列中,含左边界元素的字序列和中的最大值,_maxSubSequenceSum2中使用
*/
static int _maxLeftBoderSubSequenceSum(int a[], int left, int right)
{
    int i;
    int sum = 0;
    int maxSum = a[left];
    for (i = left; i <= right; i++)
    {
        sum += a[i];
        if (sum > maxSum)
        {
            maxSum = sum;
        }
    }
    return maxSum;
}

/*
* 某段字序列中,含右边界元素的字序列和中的最大值,_maxSubSequenceSum2中使用
*/
static int _maxRightBoderSubSequenceSum(int a[], int left, int right)
{
    int i;
    int sum = 0;
    int maxSum = a[right];
    for (i = right; i >= left; i--)
    {
        sum += a[i];
        if (sum > maxSum)
        {
            maxSum = sum;
        }
    }
    return maxSum;
}

/*
* 求序列某段子序列中子序列和最大值
*/
static int _maxSubSequenceSum2(int a[], int left, int right)
{
    int center;
    int leftMaxSum;
    int rightMaxSum;
    int maxLeftBorderSum;
    int maxRightBorderSum;

    /* 递归终止条件 */
    if (left == right)
    {
        return a[left];
    }

    /* 分治法递归开始,取中点二分处理 */
    center = (left + right) >> 1; /* center = (left + right) / 2; */

    /* 递归求左右子序列段中最大子序列和 */
    leftMaxSum = _maxSubSequenceSum2(a, left, center);
    rightMaxSum = _maxSubSequenceSum2(a, center + 1, right);

    maxLeftBorderSum = _maxRightBoderSubSequenceSum(a, left, center);
    maxRightBorderSum = _maxLeftBoderSubSequenceSum(a, center + 1, right);

    /*
     * 二分后的最大值有三个:
     *    1、leftMaxSum,左段最大子序列和
     *    2、rightMaxSum,右段最大子序列和
     *    3、maxLeftBorderSum+maxRightBorderSum,左段最大含右边界子序列和最大值和右段最大含左边界子序列和最大值,二者之和
     * 这三者中的最大值即为分段前的最大子序列和
     *
     * 分治算法核心部分,解决分治后结果归并问题,具体分析:
     *    这是对分段后的子序列的一种划分,有三种,只需分别求出各种的最大值然后在三者之间取一个最大值即可:
     *       1、子序列全在左段,最大子序列和为leftMaxSum
     *       2、子序列全在右段,最大子序列和为rightMaxSum
     *       3、子序列跨左右段,最大字序列和为maxLeftBorderSum+maxRightBorderSum
     */
    return tmax(leftMaxSum, rightMaxSum, maxLeftBorderSum+maxRightBorderSum);
}

/*
* 分治法实现,算法复杂度O(n*log(n))
* 分:使用二分法进行分段
* 治:详细算法见_maxSubSequenceSum2内描述,简述为:
*    全段最大子序列为以下三者中的最大值
*       左段最大子序列和
*       右段最大子序列和
*       左段最大含右边界子序列和最大值和右段最大含左边界子序列和最大值之和
*/
int maxSubSequenceSum2(int a[], int len)
{
    return _maxSubSequenceSum2(a, 0, len - 1);
}

/*
* 动态规划实现,算法复杂度O(n)
*/
int maxSubSequenceSum3(int a[], int len)
{
    int i;
    int curSum; /* 当前序列和 */
    int maxSum; /* 最大序列和 */

    /* 初始化当前序列和为0 */
    curSum = 0;

    /* 初始化最大子序列和为序列第一个元素 */
    maxSum = a[0];

    /* 开始循环求子序列和 */
    for (i = 0; i < len; i++)
    {
        curSum = curSum + a[i];

        /* 与最大子序列和比较,更新最大子序列和 */
        if (curSum > maxSum)
        {
            maxSum = curSum;
        }

        /* 动态规划部分,舍弃当前和为负的子序列 */
        if (curSum < 0)
        {
            curSum = 0;
        }
    }
    return maxSum;
}









PS:这是最近的一次面试中的一道分析题目,给出了本文中第二种算法,要求进行优化;由于时间段,且对本问题在先前并没多少了解,首先想到的是分治,很遗憾,归并条件想的不是很充分。





功    能:   在一个整数序列中求一个子序列,该子序列的和最大 
输入说明:   首先输入整数序列的长度 length       
       接着输入该整数序列          
输出说明:   输出子序列的起点和终点,并输出该子序列的和      
事    例:   (建议用管道测试)         
              输入文件 data.txt  内容如下:      
              8
       12 -13 1 2 23 -14 55 -2
             
       输出:
       The subsequence from 2 to 6,max sum is 67 (序列从1开始算)
评 价:  这个题可以有几种算法:o(n^3),o(n^2),o(nlogn),o(n)        
       本程序使用o(n),这种求法几乎完美 !   
语    言:   非标准 C  编译环境:VC ++ 6.0            
Author  :   江南孤峰  Time :2006--11--9      


#include <stdio.h>
#include <stdlib.h>

#include <limits.h>
#include <malloc.h>

int main(){
int *ip;
int j,length,max,sum;
int start1 = 0 ,start2 = 0;

printf("Please enter the array's length:");
scanf("%d",&length);
if((ip = (int*)malloc(length*sizeof(int)))==NULL){
  fprintf(stderr,"Malloc memory failed !");
  exit(1);
}
printf("Enter eath element:");
for(j = 0; j < length ; j ++)
  scanf("%d",ip+j);

max = INT_MIN;
for(sum = j = 0; j < length; j ++){
  sum += *(ip+j);
  if(max < sum){
   start1 = start2;
   max = sum;
  }
  if(sum < 0){
   start2 = j+1;
   sum = 0;
  }
}
for(j = start1,sum = 0; sum != max; j ++)
  sum += *(ip+j);
printf(""nThe subsequence from %d to %d,max sum is %d"n",start1,j-1,max);
return 0;
}

// 下面是网友的题目,估计也是ACM题,和上面的题差不多,所以贴这里算了

现的问题是如果求环形整数串的最大连续和子串呢? 

输入数据

本题有多组输入数据,你必须处理到EOF为止

每组数据的第一行有一个整数n, (1<=n<=1000000).第2行有n个整数,每个整数都在[-100,100]的范围内

输出数据

每组数据输出一个整数,表示环形整数串最大连续子串和。

输入样例

6
-2 3 0 1 -48 80
2
1 3
5
10 -2 -3 1 1


输出样例

82
4
12


分析:环行的 数组其实可以看做将该数组写两次,不过为了节约空间,在达到数组长度时我们可以绕到数组的起始,下面的代码就是这样。ACM的题目都强调速度,如果下面的程序不能通过,可以尝试后面那个,空间换时间。这里时间复杂度都是O(n^2)。

代码一:

#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>

#include <limits.h>

int main(){
int *ip;
int i,j,n,max,maxSave,sum;

while(scanf("%d",&n) != EOF){
  if((ip = (int*)malloc(n*sizeof(int)))==NULL){
   fprintf(stderr,"Malloc memory failed !");
   exit(1);
  }
  for(j = 0; j < n ; j++)
   scanf("%d",ip+j);
  maxSave = INT_MIN;

  for(i = 0; i < n; i++){
   for(max = sum = *(ip+i),j = i+1;j != i; j++){
    if(j == n){
     j = -1;
     continue;
    }
    sum += *(ip+j);
    if(max < sum)
     max = sum;
    if(sum < 0)
     sum = 0;
   }
   if(maxSave < max)
    maxSave = max;
  }
  printf("%d"n",maxSave);
  free(ip);
}
return 0;
}

代码二:

// 注意TC开不了这么大的数组
#include <stdio.h>

#include <limits.h>

int a[2000000];
int main(){
int i,j,n,max,maxSave,sum;

while(scanf("%d",&n) != EOF){
  for(j = 0; j < n ; j++){
   scanf("%d",&a[j]);
   a[j+n] = a[j];
  }

maxSave = INT_MIN;
  for(i = 0; i < n; i++){
   for(max = sum = 0,j = i;j < n+i; j++){
    sum += a[j];
    if(max < sum)
     max = sum;
    if(sum < 0)
     sum = 0;
   }
   if(maxSave < max)
    maxSave = max;
  }
  printf("%d"n",maxSave);
}
return 0;
}

2007 ---- 8 ----- 28   更新[来自湖大ACM]

[解题报告]:
作者:wation
解题思路:
首先考虑是否所有数据都不大于0,这种情况直接输出最大数(不证明),
然后,分别求出非环串的最大子段和和最小子段和以及和,
结果等于max(最大子段和,和-最小子段和);
证明:
设串s="s1,s2,...,sn"
构成最大子段和的串smax="si,si+1,...sk"
构成最小子段和的串smin="sj,sj+1,...sf"
首先证明smax和smin不存在公共子串。
证1:假设存在ssub="st,...sp"即属于smax,也属于smin;
首先假设ssub在smax中,必然存在smax-ssub<smax(smax-ssub表示smax中除掉ssub所构成的子串),
推出ssub>0(ssub的所有元素之和大于0)
同理假设ssub在smin中推出ssub<0;
从而推出矛盾,所以smax和smin不存在公共子串。
然后证明smax和smin要么相邻要么中间存在一个和为0的子串。
证明2:要证上述结论只要证得在smax和smin不相邻的情况下
假设存在nsub="sk+1,...,sf-1",使得nsub=0;
假设nsub>0,那么smax+nsub>smax,根据求解方法,smax必将扩充到sf-1;所以nsub<=0
同理推出nsub不能小于0;
所以推出nsub=0;
最后证明环串的最大子段和csmax=max(smax,s-smin);
证3:假设存在一个子串vsmax>csmax;
显然vsmax是两端两个子串的合并,否则必然有vsmax<=smax
设 vsmax="s1,...,sz"+"sx,...,sn"
那么sx一定>sf,因为如果假设sx<sf,即smin和vsmax存在公共子串,1已经证明不存在了;
再者sz一定小于si,如果sz>=si,根据求解方法vsmax必将继续扩展直到sz=sk,
因为"sf,...,sx"=0(2已经证得),所以有vsmax=s-smin;与假设矛盾。
a、假设csmax=smax,那么vsmax>smax,即"s1,...,sz"+"sx,...,sn">"si,si+1,...sk",
即s-smin-"sz+1,...,si-1"-smax>"si,si+1,...sk",
即s-smin-"sz+1,...,si-1"-smax>smax既-"sz+1,...,si-1"-smax>smax-(s-smin)>0,因为那么根据求解思路smin应该
="sz+1,...,sf",所以矛盾
b、假设csmax=s-smin,那么vsmax>s-smin,既s-smin-"sz+1,...,si-1"-smax>s-smin,即-"sz+1,...,si-1"-smax>0,
同理得出矛盾。
从证1,证2,证3得证结论环串最优解等于max(最大子段和,和-最小子段和);
至于求非环串最大最小子段和可以用dp算法。

下面是我根据上面思路写的代码:

#include <iostream>
using namespace std;

int main(){
    int n,i,amax,amin,submax,submin,sum,t;
    for(;cin>>n;){
        cin>>t;
        sum = submin = amin = submax = amax = t;
        for(i=1; i<n; i++){
            cin>>t;
            sum += t;
            amax = amax>0 ? amax+t : t;
            if(amax > submax)
                submax = amax;
            amin = amin<0 ? amin+t : t;
            if(amin < submin)
                submin = amin;
        }
        if(sum-submin > submax && sum!=submin)
            cout<<(sum-submin)<<endl;
        else
            cout<<submax<<endl;
    }
    return 0;
}

Language : GNU C++ , Judge Result: Time Limit Exceeded

#include <stdio.h>

int main(){
    int n,i,amax,amin,submax,submin,sum,t;
    for(;scanf("%d",&n)!=EOF;){
        scanf("%d",&t);
        sum = submin = amin = submax = amax = t;
        for(i=1; i<n; i++){
            scanf("%d",&t);
            sum += t;
            amax = amax>0 ? amax+t : t;
            if(amax > submax)
                submax = amax;
            amin = amin<0 ? amin+t : t;
            if(amin < submin)
                submin = amin;
        }
        if(sum-submin > submax && sum!=submin)
            printf("%d"n",sum-submin);
        else
            printf("%d"n",submax);
    }
    return 0;
}


分享到:
评论

相关推荐

    最大子序列和问题求解源代码

    2010.09.07 用分治法求解最大子序列问题。...《数据结构与算法分析 C++描述》p42最大子序列问题的递归方法代码 2010.09.07 vector a的内容: 4 -3 5 -2 -1 2 6 -2 最大子序列和是:11 请按任意键继续. . .

    最大子序列之和C++实现常数时间

    在编程领域,最大子序列和问题(Max Subarray Problem)是一个经典的动态规划问题,它要求在给定的一组整数序列中找到具有最大和的连续子序列。这个问题在实际应用中有着广泛的意义,例如在金融分析、数据分析等领域...

    C++算法-最大子序列和.zip

    本资料包“C++算法-最大子序列和.zip”聚焦于C++实现的一种经典算法——最大子序列和(Maximum Subarray Problem),这在数据结构与算法的学习中是非常关键的一环。 最大子序列和问题是一个寻找一串数组中具有最大...

    c/c++解决最大子序列和问题

    利用C/C++语言解决最大子列和问题,在线处理-超简单的算法

    最大子序列和问题的求解.md

    ### 最大子序列和问题详解 #### 一、引言 最大子序列和问题是一个经典的计算机科学问题,涉及在一串整数(其中可能包括负数)中找到具有最大和的连续子序列。此问题不仅在理论研究中有重要意义,在实际应用如生物...

    最大子序列和MAX-SUM

    最大子序列和问题,一个整形数组序列求一个不变顺序的相加最大和子序列。

    最大子序列和问题 C++ 代码实现

    最大子序列和问题(Maximum Subarray Sum Problem)是求解一个数组中连续子数组的和的最大值的问题。

    最大子序列求和最大子序列求和

    动态规划是解决最大子序列和问题的最优算法之一。其核心思想是利用已计算出的子问题结果,避免重复计算,从而达到优化算法的目的。在这个问题中,我们可以通过维护一个变量,用来存储当前子序列的最大和,每当遇到新...

    C 最大子序列算法

    C 最大子序列问题的几中算法-分治-联机算法

    动态规划算法:最大子序列问题

    动态规划算法:最大子序列问题

    最大子序列.pdf

    例如,注释提到的“最大子序列问题”,实际上在代码中似乎是在计算连续子序列的和,而不是找出最大子序列本身。此外,代码中有些地方语法不完整,可能是因为扫描错误,例如`intsum=0;for(i=0;i;i++){}mostEle(num);`...

    最大子序列问题算法分析.doc

    最大子序列问题算法分析 最大子序列问题是计算机科学中的一种经典问题,旨在寻找给定整数序列中最大子序列的和。该问题可以使用多种算法来解决,包括穷举法、递归法等。在本文中,我们将对最大子序列问题的算法进行...

    西南交通大学-算法分析与设计-实验5.4实验报告包含预习部分-求最大子序列-求最大子矩阵

    最大子序列和问题是一维数组中最长的连续子序列,使得子序列的和最大。最大子矩阵和问题是在二维矩阵中找到一个矩形区域,其内部元素之和最大。 1. **最大子序列和算法**: 在动态规划方法中,我们通常使用Kadane'...

    分治法求最大子数组以及其对应的下标.rar_well4fw_分治法_分治法求下标

    这个问题是经典的计算机科学问题,也被称为“最大子序列和问题”(Maximum Subarray Problem),最早由著名计算机科学家Dijkstra提出。 解决这个问题的一个经典算法是 Kadane's Algorithm,但这里我们关注的是分治...

    最大子序列算法[整理].pdf

    最大子序列问题是一种经典的算法问题,它涉及到对一维数组中的连续子序列进行求和,目标是找到和最大的那个子序列。在这个问题中,我们通常使用动态规划的思想来解决。以下是对最大子序列求和算法的详细解释: 1. *...

    最大字段和问题 分治法.cpp.rar

    **最大字段和问题**,也被称为**最大子序列和问题**,是指在给定的一组数字中找到一个连续子序列(或子数组),使得这个子序列的和最大。这个问题在实际应用中非常常见,比如在金融数据分析中寻找收益最大的投资组合...

    最大子段和(分治法)源码

    最大子段和问题是指给定一个由 n 个整数(可能有负整数)组成的序列(a1, a2, …, an),求该序列中的最大子段和。 知识点: 1. Maximum Subarray Problem:最大子段和问题是指给定一个由 n 个整数(可能有负整数...

    动态规划最大子序列和 Gabe

    最大子序列和问题是一种经典的动态规划问题,它可以通过使用动态规划算法来解决。 最大子序列和问题可以描述为:给定一个序列{ N1, N2, ..., NK },找到其中的最大连续子序列,使其元素和最大。例如,给定序列{ -2,...

Global site tag (gtag.js) - Google Analytics