zoukankan      html  css  js  c++  java
  • 最大子序列和问题

     [question]:给定整数(A_1,A_2,A_3,...,A_N)(可能有负数),求(sum_{k=i}^{j}A_k)的最大值(方便起见,若所有整数为负数,则最大序列和为0)

         //例如:输入(-2,11,-4,13,-5,-2),答案为(20)(从(A_2)到(A_4))

      解法①:穷举法

          外两层循环遍历数组,确定序列上下界。最内层循环遍历序列求和。

     1 /*
     2  * maxSubSum1:解法①,穷举法
     3  * 输入数组a
     4  * 返回最大子序列和
     5  */
     6 int maxSubSum1( const vector<int> &a )
     7 {
     8     int maxSum = 0;//存储结果
     9     
    10     for (int i = 0; i < a.size(); i++)
    11         for (int j = i; j < a.size(); j++)
    12         {
    13             int thisSum = 0;
    14             
    15             for (int k = i; k <= j; k++)
    16                 thisSum += a[k];
    17             
    18             if (thisSum > maxSum)
    19             {
    20                 maxSum = thisSum;
    21             }
    22         }
    23     
    24     return maxSum;
    25 }

        显然,此方法时间复杂度为(O(N^3)),并没有什么实际意义,(N)取较大值时算法效果很差。   

      解法②:改进穷举法

        第二层每次循环顺便求出子序列值,并判断结果,取消第三层循环。

     1 /*
     2  * maxSubSum2:解法②,改进穷举法
     3  * 输入数组a
     4  * 返回最大子序列和
     5  */
     6 int maxSubSum2( const vector<int> &a )
     7 {
     8     int maxSum = 0;//存储结果
     9     
    10     for (int i = 0; i < a.size(); i++)
    11     {
    12         int thisSum = 0;
    13         
    14         for (int j = i; j < a.size(); j++)
    15         {
    16             thisSum += a[j];
    17             
    18             if (thisSum > maxSum)
    19                 maxSum = thisSum;
    20         }
    21     }
    22     
    23     return maxSum;
    24 }

         显然,此方法时间复杂度为(O(N^2)),在穷举法的基础上稍作改进,(N)取较大值时算法效果仍然很差。 

      解法③:分治策略

        将原问题分成两个大致相等的子问题,然后递归的对他们求解。最大子序列可能出现的地方:输入数据的左半部,输入数据的右半部,跨越数据中部占据左右两半部分。

     1 /*
     2  * max3:递归函数
     3  * 输入数组a,递归下界,递归上界
     4  * 返回一次递归最大子序列和
     5  */
     6 int maxSumRec(const vector<int> &a, int left, int right)
     7 {
     8     if (left == right)//基准情况
     9     {
    10         if (a[left] > 0)//只有一个元素时返回其值(元素全为负时返回0)
    11             return a[left];
    12         else
    13             return 0;
    14     }
    15     
    16     int center = (left + right) / 2;
    17     int maxLeftSum = maxSumRec(a, left, center);
    18     int maxRightSum = maxSumRec(a, center + 1, right);
    19     
    20     int maxLeftBorderSum = 0, leftBorderSum = 0;
    21     for (int i = center; i >= left; i--)
    22     {
    23         leftBorderSum += a[i];
    24         if (leftBorderSum > maxLeftBorderSum)
    25             maxLeftBorderSum = leftBorderSum;
    26     }//前半部分最大子序列和
    27     
    28     int maxRightBorderSum = 0, rightBorderSum = 0;
    29     for (int j = center + 1; j <= right; j++)
    30     {
    31         rightBorderSum += a[j];
    32         if (rightBorderSum > maxRightBorderSum)
    33             maxRightBorderSum = rightBorderSum;
    34     }//后半部分最大子序列和
    35     
    36     return max3(maxLeftSum, maxRightSum, maxLeftBorderSum + maxRightBorderSum);//max3为求三个整型数的最大值函数
    37 }
    38 
    39 /*
    40  * maxSubSum3:解法③,分治策略
    41  * 输入数组a
    42  * 返回最大子序列和
    43  */
    44 int maxSubSum3( const vector<int> &a )
    45 {
    46     return maxSumRec(a,0,a.size() - 1);
    47 }

      由于使用了递归,且递归函数内使用了一层循环,所以算法时间复杂度为(O(Nlog_2N))。此时已经为较理想的结果。  

    解法④:终极方法

      再次改进穷举法,显然我们可以推出一个结论,如果(a[i])是负的,那么它不可能代表最优序列的起点,因为任何包含(a[i])的作为起点的子序列都可以通过用(a[i+1])做起点而得到改进。类似地,任何负的

    子序列不可能是最优子序列的前缀。如果在内循环中检测到(a[i])到(a[j])的子序列是负的,那么我们可以推进(i)。关键结论是:我们不仅可以把(i)推进到(i+1),而且我们实际上还可以把它一直推进到(j+1)。为了证明,我们由前面的结论知道(a[i])是非负的,其次(j)是使从下标(i)开始使序列和为负的第一个下标,令(p)为(i+1)到(j)之间的任一下标,那么从下标(i)到(j)中,序列(a[i]...a[p-1])的和始终大于序列(a[p]...a[j])的和。因此把(i)推进到(j+1)是安全的,我们不会错过最优解。

     1 /*
     2  * maxSubSum4:解法④,终极方法
     3  * 输入数组a
     4  * 返回最大子序列和
     5  */
     6 int maxSubSum4( const vector<int> &a )
     7 {
     8     int maxSum = 0, thisSum = 0;
     9     
    10     for (int j = 0; j < a.size(); j++)
    11     {
    12         thisSum += a[j];
    13         
    14         if (thisSum > maxSum)
    15             maxSum = thisSum;
    16         else if (thisSum < 0)//关键步骤:推进到j+1(使序列和小于0下标j)
    17             thisSum = 0;
    18         else
    19             continue;
    20     }
    21     
    22     return maxSum;
    23 }

      显然,算法的时间复杂度为(O(N)),并且只对数据一次扫描,一旦(a[i])被读入并处理,它就不需要被记忆,因此仅需常量空间并以线性时间运行的联机算法

    总结:要善于思考问题的结构,仔细研究数据的结构寻找最优解法。没有思路时也可以先写出最笨的方法,然后逐步改进。

    参考:数据结构与算法分析(c++描述).第三版.【美】Mark Allen Weiss.人民邮电出版社

    欢迎交流指正,欢迎转载,转载请注明作者及出处。

  • 相关阅读:
    numpy 基础 —— np.linalg
    图像旋转后显示不完全
    opencv ---getRotationMatrix2D函数
    PS1--cannot be loaded because the execution of scripts is disabled on this system
    打开jnlp Faild to validate certificate, the application will not be executed.
    BATCH(BAT批处理命令语法)
    oracle vm virtualbox 如何让虚拟机可以上网
    merge 实现
    Windows batch,echo到文件不成功,只打印出ECHO is on.
    python2.7.6 , setuptools pip install, 报错:UnicodeDecodeError:'ascii' codec can't decode byte
  • 原文地址:https://www.cnblogs.com/nslogmeng/p/4461603.html
Copyright © 2011-2022 走看看