<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html><head><meta http-equiv="Cont

简介: 最大连续子序列和问题        给定k个整数的序列{N1,N2,…,Nk },其任意连续子序列可表示为{ Ni, Ni+1, …, Nj },其中 1 a&&b>c) return b; else return c; }       现在对上面的代码进行相关说明:       Center变量所确定的值将处理序列分割为两部分,一部分为Center前半部,一部分为Center+1后半部。

最大连续子序列和问题

        给定k个整数的序列{N1,N2,…,Nk },其任意连续子序列可表示为{ Ni, Ni+1, …, Nj },其中 1 <= i <= j <= k。最大连续子序列是所有连续子序中元素和最大的一个,例如给定序列{ -2, 11, -4, 13, -5, -2 },其最大连续子序列为{11,-4,13},最大连续子序列和即为20。

注:为方便起见,如果所有整数均为负数,则最大子序列和为0。

解决这样一个问题是一个很有趣的过程,我们可以尝试着从复杂度比较高的算法一步一步地推出复杂度较低的算法。

 

算法一:

       时间复杂度:O(N^3)

       其代码:

int MaxSubSequence(const int A[], int N){
    int ThisSum,MaxSum,i,j,k;
    MaxSum = 0;
    for(i=0;i<N;i++)
    {
        for(j=i;j<N;j++)
        {
            ThisSum = 0;
            for(k=i;k<=j;k++)
            {
                ThisSum += A[k];
            }
            if(ThisSum > MaxSum)
                MaxSum = ThisSum;
        }
    }
    return MaxSum;
} 
        对于此种算法,其主要方法是穷举法,即求出该序列所有子序列的序列和,然后取最大值即可。

 

算法二:

       时间复杂度:O(N^2)

       其代码:

int MaxSubSequence(const int A[], int N){
    int ThisSum,MaxSum,i,j;
    MaxSum = 0;
    for(i=0;i<N;i++)
    {
        ThisSum = 0;
        for(j=i;j<N;j++)
        {
            ThisSum += A[j];
            if(ThisSum > MaxSum)
                MaxSum = ThisSum;
        }
    }
    return MaxSum;
}
        对于这种方法,归根究底还是属于穷举法,其间接地求出了所有的连续子序列的和,然后取最大值即可。

       那么,这里,我们需要对比一下前面两种算法,为什么同样都是穷举法,但算法一的时间复杂度远高于算法二的时间复杂度?

       算法二相较于算法一,其优化主要体现在减少了很多重复的操作。

       对于A-B-C-D这样一个序列,

       算法一在计算连续子序列和的时候,其过程为:

       A-B、A-C、A-D、B-C、B-D、C-D

       而对于算法二,其过程为:

       A-B、A-C、A-D、B-C、B-D、C-D

       其过程貌似是一样的,但是算法一的复杂就在于没有充分利用前面已经求出的子序列和的值。

       举个例子,算法一在求A-D连续子序列和的值时,其过程为A-D = A-B + B-C + C-D;

       而对于算法二,A-D连续子序列和的求值过程为A-D = A-C+C-D;

       这样,算法二充分利用了前面的计算值,这样就大大减少了计算子序列和的步骤。

 

算法三:递归法(分治法)

       时间复杂度:O(NlogN)

       易知,对于一数字序列,其最大连续子序列和对应的子序列可能出现在三个地方。或是整个出现在输入数据的前半部(左),或是整个出现在输入数据的后半部(右),或是跨越输入数据的中部从而占据左右两半部分。前两种情况可以通过递归求解,第三种情况可以通过求出前半部分的最大和(包含前半部分的最后一个元素)以及后半部分的最大和(包含后半部分的第一个元素)而得到,然后将这两个和加在一起即可。

       其实现代码为:

int MaxSubSequence(const int A[],int N)
{
    return MaxSubSum(A,0,N-1);
}

static int MaxSubSum(const int A[], int Left, int Right)
{
    int MaxLeftSum,MaxRightSum;
    int MaxLeftBorderSum,MaxRightBorderSum;
    int LeftBorderSum,RightBorderSum;
    int Center,i;

    if(Left == Right)
    {
        if(A[Left] > 0)
            return A[Left];
        else
            return 0;
    }

    Center = (Left + Right)/2;
    MaxLeftSum = MaxSubSequence(A,Left,Center);
    MaxRightSum = MaxSubSequence(A,Center+1,Right);

    MaxLeftBorderSum = 0;
    LeftBorderSum = 0;
    for(i = Center;i >= Left;i--)
    {
        LeftBorderSum += A[i];
        if(LeftBorderSum > MaxLeftBorderSum)
            MaxLeftBorderSum = LeftBorderSum;
    }

    MaxRightBorderSum = 0;
    RightBorderSum = 0;
    for(i = Center+1;i <= Right;i++)
    {
        RightBorderSum += A[i];
        if(RightBorderSum > MaxRightBorderSum)
            MaxRightBorderSum = RightBorderSum;
    }   

    return Max(MaxLeftSum,MaxRightSum,MaxLeftBorderSum + MaxRightBorderSum);
} 

int Max(int a, int b, int c)
{
    if(a>b&&a>c)
        return a;
    else if(b>a&&b>c)
        return b;
    else
        return c; 
}
        现在对上面的代码进行相关说明:

        Center变量所确定的值将处理序列分割为两部分,一部分为Center前半部,一部分为Center+1后半部。

       在上文,我们提到,最大连续子序列的出现位置有三种情况。

       对于前两种情况,我们根据递归特性,可以得到:

   MaxLeftSum = MaxSubSequence(A,Left,Center);
    MaxRightSum = MaxSubSequence(A,Center+1,Right);
        而对于第三种情况,我们需要先求出前半部包含最后一个元素的最大子序列:
 MaxLeftBorderSum = 0;
    LeftBorderSum = 0;
    for(i = Center;i >= Left;i--)
    {
        LeftBorderSum += A[i];
        if(LeftBorderSum > MaxLeftBorderSum)
            MaxLeftBorderSum = LeftBorderSum;
    }
        然后,再求出后半部包含第一个元素的最大子序列:

   MaxRightBorderSum = 0;
    RightBorderSum = 0;
    for(i = Center+1;i <= Right;i++)
    {
        RightBorderSum += A[i];
        if(RightBorderSum > MaxRightBorderSum)
            MaxRightBorderSum = RightBorderSum;
    }   
        最后,我们只需比较这三种情况所求出的最大连续子序列和,取最大的一个,即可得到需要求解的答案。
return Max(MaxLeftSum,MaxRightSum,MaxLeftBorderSum + MaxRightBorderSum);
     我们在介绍这个算法的开始,就已经提到了其时间复杂度,现在做一个推导:

     令T(N)是求解大小为N的最大连续子序列和问题所花费的时间。

     当N==1时,T(1) = 1;

     当N>1时,T(N) = T(N/2) + O(N);

     有数学推导公式,我们可以得到:

     T(N) = NlogN + N =O(NlogN)。

 

算法四:动态规划法

       时间复杂度:O(N)

       终于到了动态规划的部分了,这么一步一步走来,感受到了算法的无穷魅力。那么如何用动态规划来处理这个问题?

       首先,我们重温将一个问题用动态规划方法处理的准则:

       “最优子结构”、“子问题重叠”、“边界”和“子问题独立”。

       在本问题中,我们可以将子序列与其子子序列进行问题分割。

       最后得到的状态转移方程为:            

       MaxSum[i] = Max{ MaxSum[i-1] + A[i], A[i]};

       在这里,我们不必设置数组MaxSum[]。

代码实现:

int MaxSubSequence(const int A[], int N)
{
    int ThisSum,MaxSum,j;
    ThisSum = MaxSum =0;
    for(j = 0;j < N;j++)
    {
        ThisSum += A[j];

        if(ThisSum > MaxSum)
            MaxSum = ThisSum;
        else if(ThisSum < 0)
            ThisSum = 0; 
    }
    return MaxSum; 
} 
        在本代码实现中,ThisSum持续更新,同时整个过程,只对数据进行了一次扫描,一旦A[i]被读入处理,它就不再需要被记忆。(联机算法)

小结:

       整个过程是一个思想的选择问题,从最初的穷举法,到分治法,再到动态规划法。算法设计思想的灵活选择是处理一个实际问题的关键。

目录
相关文章
|
4天前
|
弹性计算 安全 API
访问控制(RAM)|云上安全使用AccessKey的最佳实践
集中管控AK/SK的生命周期,可以极大降低AK/SK管理和使用成本,同时通过加密和轮转的方式,保证AK/SK的安全使用,本次分享为您介绍产品原理,以及具体的使用步骤。
101780 0
|
4天前
|
SQL 关系型数据库 分布式数据库
Doodle Jump — 使用Flutter&Flame开发游戏真不错!
用Flutter&Flame开发游戏是一种什么体验?最近网上冲浪的时候,我偶然发现了一个国外的游戏网站,类似于国内的4399。在浏览时,我遇到了一款经典的小游戏:Doodle Jump...
|
11天前
|
弹性计算 运维 安全
访问控制(RAM)|云上程序使用临时凭证的最佳实践
STS临时访问凭证是阿里云提供的一种临时访问权限管理服务,通过STS获取可以自定义时效和访问权限的临时身份凭证,减少长期访问密钥(AccessKey)泄露的风险。本文将为您介绍产品原理,以及具体的使用步骤。
151032 4
|
10天前
|
数据采集 存储 运维
提升团队工程交付能力,从“看见”工程活动和研发模式开始
本文从统一工程交付的概念模型开始,介绍了如何将应用交付的模式显式地定义出来,并通过工具平台落地。
119990 57
|
10天前
|
监控 负载均衡 Java
深入探究Java微服务架构:Spring Cloud概论
**摘要:** 本文深入探讨了Java微服务架构中的Spring Cloud,解释了微服务架构如何解决传统单体架构的局限性,如松耦合、独立部署、可伸缩性和容错性。Spring Cloud作为一个基于Spring Boot的开源框架,提供了服务注册与发现、负载均衡、断路器、配置中心、API网关等组件,简化了微服务的开发、部署和管理。文章详细介绍了Spring Cloud的核心模块,如Eureka、Ribbon、Hystrix、Config、Zuul和Sleuth,并通过一个电商微服务系统的实战案例展示了如何使用Spring Cloud构建微服务应用。
103499 8
|
11天前
|
人工智能 Serverless 对象存储
让你的文档从静态展示到一键部署可操作验证
通过函数计算的能力让阿里云的文档从静态展示升级为动态可操作验证,用户在文档中单击一键部署可快速完成代码的部署及测试。这一改变已在函数计算的活动沙龙中得到用户的认可。
120817 214
|
11天前
|
SQL 存储 数据可视化
Ganos H3地理网格能力解析与最佳实践
本文介绍了Ganos H3的相关功能,帮助读者快速了解Ganos地理网格的重要特性与应用实践。H3是Uber研发的一种覆盖全球表面的二维地理网格,采用了一种全球统一的、多层次的六边形网格体系来表示地球表面,这种地理网格技术在诸多业务场景中得到广泛应用。Ganos不仅提供了H3网格的全套功能,还支持与其它Ganos时空数据类型进行跨模联合分析,极大程度提升了客户对于时空数据的挖掘分析能力。
|
11天前
|
存储 缓存 安全
深度解析JVM世界:JVM内存结构
深度解析JVM世界:JVM内存结构