<!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

本文涉及的产品
转发路由器TR,750小时连接 100GB跨地域
简介: 最大连续子序列和问题        给定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]被读入处理,它就不再需要被记忆。(联机算法)

小结:

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

目录
相关文章
|
Web App开发 前端开发 Java
<!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
 Connection reset by peer的常见原因: 1)服务器的并发连接数超过了其承载量,服务器会将其中一些连接关闭;    如果知道实际连接服务器的并发客户数没有超过服务器的承载量,看下有没有网络流量异常。
862 0
|
Web App开发 存储 前端开发
<!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
NoSuchObjectException(message:There is no database named cloudera_manager_metastore_canary_test_db_hive_hivemetastore_df61080e04cd7eb36c4336f71b5a8bc4) at org.
1082 0
|
Web App开发 前端开发
<!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
service cloudera-scm-agent stop service cloudera-scm-agent stop umount /var/run/cloudera-scm-agent/process umo...
764 0
|
Web App开发 前端开发 数据库
|
Web App开发 前端开发
|
Web App开发 前端开发 关系型数据库
<!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
如果mysql正在运行,/etc/init.d/mysqld stop 启动mysql(无需输入密码):bin/safe_mysqld –skip-grant-tables & 在bin目录下执行mysql,此时无需输入密...
808 0
|
Web App开发 前端开发 数据库
<!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
数据仓库建设步骤Posted on 2015-03-04 10:18 xuzhengzhu 阅读(1164) 评论(0) 编辑 收藏 1.系统分析,确定主题 确定一下几个因素:    ·操作出现的频率,即业务部门每隔多长时间做一次查询分析。
861 0
|
Web App开发 前端开发
<!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
在统计分析系统中, 维度:指人们分析事物的角度。比如,分析活跃用户,可以从时间的维度,也可以从地域的维度去看,也可以时间、地域两个维度组合去分析。
668 0