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.人民邮电出版社

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

  • 相关阅读:
    HDU1213How Many Tables并查集
    Redis新的存储模式diskstore
    GPFS文件系统笔记
    redis持久化
    360安全卫士qurl.f.360.cn分析
    [原创]解决DataSet的GetXml()方法空列不返回问题
    在多台服务器上简单实现Redis的数据主从复制
    史航416随笔
    史航416实验1总结
    史航416第二次作业总结
  • 原文地址:https://www.cnblogs.com/nslogmeng/p/4461603.html
Copyright © 2011-2022 走看看