算法笔记——【动态规划】最大子段和问题

来源:互联网 发布:淘宝达人报名入口 编辑:程序博客网 时间:2024/05/20 23:34

    1、最大子段和问题

     问题定义:对于给定序列a1,a2,a3……an,寻找它的某个连续子段,使得其和最大。如( -2,11,-4,13,-5,-2 )最大子段是{ 11,-4,13 }其和为20。

     (1)枚举法求解

     枚举法思路如下:

     以a[0]开始: {a[0]}, {a[0],a[1]},{a[0],a[1],a[2]}……{a[0],a[1],……a[n]}共n个

     以a[1]开始: {a[1]}, {a[1],a[2]},{a[1],a[2],a[3]}……{a[1],a[2],……a[n]}共n-1个

     ……

     以a[n]开始:{a[n]}共1个

     一共(n+1)*n/2个连续子段,使用枚举,那么应该可以得到以下算法:
     具体代码如下:

[cpp] view plain copy
  1. //3d4-1 最大子段和问题的简单算法  
  2. #include "stdafx.h"  
  3. #include <iostream>   
  4. using namespace std;   
  5.   
  6. int MaxSum(int n,int *a,int& besti,int& bestj);  
  7.   
  8. int main()  
  9. {  
  10.     int a[] = {-2,11,-4,13,-5,-2};  
  11.   
  12.     for(int i=0; i<6; i++)  
  13.     {  
  14.         cout<<a[i]<<" ";  
  15.     }  
  16.   
  17.     int besti,bestj;  
  18.   
  19.     cout<<endl;  
  20.     cout<<"数组a的最大连续子段和为:a["<<besti<<":"<<bestj<<"]:"<<MaxSum(6,a,besti,bestj)<<endl;  
  21.   
  22.     return 0;  
  23. }  
  24.   
  25. int MaxSum(int n,int *a,int& besti,int& bestj)  
  26. {     
  27.     int sum = 0;  
  28.     for(int i=0; i<n; i++)//控制求和起始项  
  29.     {  
  30.         for(int j=i; j<n; j++)//控制求和结束项  
  31.         {  
  32.             int thissum = 0;  
  33.             for(int k=i; k<=j; k++)//求和  
  34.             {  
  35.                 thissum += a[k];  
  36.             }  
  37.   
  38.             if(thissum>sum)//求最大子段和  
  39.             {  
  40.                 sum = thissum;  
  41.                 besti = i;  
  42.                 bestj = j;  
  43.             }  
  44.         }  
  45.     }  
  46.     return sum;  
  47. }  

            从这个算法的三个for循环可以看出,它所需要的计算时间是O(n^3)。事实上,如果注意到,则可将算法中的最后一个for循环省去,避免重复计算,从而使算法得以改进。改进后的代码如下:

[cpp] view plain copy
  1. //3d4-2 最大子段和问题的避免重复的简单算法  
  2. #include "stdafx.h"  
  3. #include <iostream>   
  4. using namespace std;   
  5.   
  6. int MaxSum(int n,int *a,int& besti,int& bestj);  
  7.   
  8. int main()  
  9. {  
  10.     int a[] = {-2,11,-4,13,-5,-2};  
  11.   
  12.     for(int i=0; i<6; i++)  
  13.     {  
  14.         cout<<a[i]<<" ";  
  15.     }  
  16.   
  17.     int besti,bestj;  
  18.   
  19.     cout<<endl;  
  20.     cout<<"数组a的最大连续子段和为:a["<<besti<<":"<<bestj<<"]:"<<MaxSum(6,a,besti,bestj)<<endl;  
  21.   
  22.     return 0;  
  23. }  
  24.   
  25. int MaxSum(int n,int *a,int& besti,int& bestj)  
  26. {     
  27.     int sum = 0;  
  28.     for(int i=0; i<n; i++)//控制求和起始项  
  29.     {  
  30.         int thissum = 0;  
  31.         for(int j=i; j<=n; j++)//控制求和结束项  
  32.         {  
  33.             thissum += a[j];//求和  
  34.             if(thissum>sum)  
  35.             {  
  36.                 sum = thissum;  
  37.                 besti = i;  
  38.                 bestj = j;  
  39.             }  
  40.               
  41.         }  
  42.     }  
  43.     return sum;  
  44. }  

     (2)分治法求解

       分治法思路如下:

    将序列a[1:n]分成长度相等的两段a[1:n/2]和a[n/2+1:n],分别求出这两段的最大字段和,则a[1:n]的最大子段和有三中情形:

    [1]、a[1:n]的最大子段和与a[1:n/2]的最大子段和相同; 

       [2]、a[1:n]的最大子段和与a[n/2+1:n]的最大子段和相同;

    [3]、a[1:n]的最大字段和为,且1<=i<=n/2,n/2+1<=j<=n。

    可用递归方法求得情形[1],[2]。对于情形[3],可以看出a[n/2]与a[n/2+1]在最优子序列中。因此可以在a[1:n/2]中计算出,并在a[n/2+1:n]中计算出。则s1+s2即为出现情形[3]时的最优值。

     具体代码如下:

[cpp] view plain copy
  1. //3d4-1 最大子段和问题的分治算法  
  2. #include "stdafx.h"  
  3. #include <iostream>   
  4. using namespace std;   
  5.   
  6. int MaxSubSum(int *a,int left,int right);  
  7. int MaxSum(int n,int *a);  
  8.   
  9. int main()  
  10. {  
  11.     int a[] = {-2,11,-4,13,-5,-2};  
  12.   
  13.     for(int i=0; i<6; i++)  
  14.     {  
  15.         cout<<a[i]<<" ";  
  16.     }  
  17.   
  18.     cout<<endl;  
  19.     cout<<"数组a的最大连续子段和为:"<<MaxSum(6,a)<<endl;  
  20.   
  21.     return 0;  
  22. }  
  23.   
  24. int MaxSubSum(int *a,int left,int right)  
  25. {     
  26.     int sum = 0;  
  27.     if(left == right)  
  28.     {  
  29.         sum = a[left]>0?a[left]:0;  
  30.     }  
  31.     else  
  32.     {  
  33.         int center = (left+right)/2;  
  34.         int leftsum = MaxSubSum(a,left,center);  
  35.         int rightsum = MaxSubSum(a,center+1,right);  
  36.   
  37.         int s1 = 0;  
  38.         int lefts = 0;  
  39.         for(int i=center; i>=left;i--)  
  40.         {  
  41.             lefts += a[i];  
  42.             if(lefts>s1)  
  43.             {  
  44.                 s1=lefts;  
  45.             }  
  46.         }  
  47.   
  48.         int s2 = 0;  
  49.         int rights = 0;  
  50.         for(int i=center+1; i<=right;i++)  
  51.         {  
  52.             rights += a[i];  
  53.             if(rights>s2)  
  54.             {  
  55.                 s2=rights;  
  56.             }  
  57.         }  
  58.         sum = s1+s2;  
  59.         if(sum<leftsum)  
  60.         {  
  61.             sum = leftsum;  
  62.         }  
  63.         if(sum<rightsum)  
  64.         {  
  65.             sum = rightsum;  
  66.         }  
  67.   
  68.     }  
  69.     return sum;  
  70. }  
  71.   
  72. int MaxSum(int n,int *a)  
  73. {  
  74.     return MaxSubSum(a,0,n-1);  
  75. }  

     算法所需的计算时间T(n)满足一下递归式:

     解此递归方程可知:T(n)=O(nlogn)。

     (3)动态规划算法求解

    算法思路如下:

    记,则所求的最大子段和为:

    由b[j]的定义知,当b[j-1]>0时,b[j]=b[j-1]+a[j],否则b[j]=a[j]。由此可得b[j]的动态规划递推式如下:

     b[j]=max{b[j-1]+a[j],a[j]},1<=j<=n。

     具体代码如下:

[cpp] view plain copy
  1. //3d4-1 最大子段和问题的动态规划算法  
  2. #include "stdafx.h"  
  3. #include <iostream>   
  4. using namespace std;   
  5.   
  6. int MaxSum(int n,int *a);  
  7.   
  8. int main()  
  9. {  
  10.     int a[] = {-2,11,-4,13,-5,-2};  
  11.   
  12.     for(int i=0; i<6; i++)  
  13.     {  
  14.         cout<<a[i]<<" ";  
  15.     }  
  16.   
  17.     cout<<endl;  
  18.     cout<<"数组a的最大连续子段和为:"<<MaxSum(6,a)<<endl;  
  19.   
  20.     return 0;  
  21. }  
  22.   
  23. int MaxSum(int n,int *a)  
  24. {  
  25.     int sum=0,b=0;  
  26.     for(int i=1; i<=n; i++)  
  27.     {  
  28.         if(b>0)  
  29.         {  
  30.             b+=a[i];  
  31.         }  
  32.         else  
  33.         {  
  34.             b=a[i];  
  35.         }  
  36.         if(b>sum)  
  37.         {  
  38.             sum = b;  
  39.         }  
  40.     }  
  41.     return sum;  
  42. }  

     上述算法的时间复杂度和空间复杂度均为O(n)。

0 0