最大连续子序和

最大连续子序列算是一个很经典的问题,它有多种算法可以实现,现在学习一下

 

算法一(暴力)O(N^3)

  就是遍历完所有的子串,求出其中的最大值并保存。复杂度为O(N^3),臃肿且容易超时

  代码如下:

 1 #include <iostream>
 2 
 3 using namespace std;
 4 int a[1000];
 5 int len;
 6 
 7 int site(int a[],int len)//复杂度为O(N^3)遍历完每个子序列
 8 {
 9     int maxs=0;
10     for(int i=0;i<len;i++)//子序列起始处
11     {
12         for(int j=i;j<len;j++)//子序列结束处
13         {
14             int sum=0;
15             for(int k=i;k<j;k++)//遍历子序列
16             {
17                 sum+=a[k];
18             }
19             if(sum>maxs)
20             {
21                 maxs=sum;
22             }
23         }
24     }
25     return maxs;
26 }
27 
28 void showa(int a[],int len)//用于查看数组内元素
29 {
30     for(int i=0;i<len;i++)
31     {
32         cout<<a[i]<<' ';
33     }
34     cout<<endl;
35 }
36 
37 int main()
38 {
39     while(cin>>len&&len)//首先输入一个M代表有M个数据
40     {
41         for(int i=0;i<len;i++)
42         {
43             cin>>a[i];
44         }
45         showa(a,len);
46         cout<<site(a,len)<<endl;
47     }
48     return 0;
49 }

其中site函数为计算的关键,我们将数组假设为一条线长,其计算顺序为:

总长:  ------------------------------

计算中:

第一次:  i----j                                   以i为0时,以j为终点,k为起点,逼近j

第二次:     k--j

…………:       k-j

即设立k为起点,j为终点,k不断逼近 即得每个子序列长度,再判断,但该算法中间重复计算了很多次相同的子序列长,因而我们可以优化一下

 

算法二(略微优化) 0(N^2)

代码如下:

#include <iostream>
#include <algorithm>
using namespace std;
int a[1000];
int len;
int  site2(int a[],int len)
{
    int maxs=0;
    for(int i=0;i<len;i++)//起点
    {
        int sum=0;
        for(int j=i;j<len;j++)//终点
        {
            sum+=a[j];
            if(maxs<sum)
            {
                maxs=sum;
            }
        }
    }
    return maxs;
}


void showa(int a[],int len)
{
    for(int i=0;i<len;i++)
    {
        cout<<a[i]<<' ';
    }
    cout<<endl;
}

int main()
{
    while(cin>>len&&len)
    {
        for(int i=0;i<len;i++)
        {
            cin>>a[i];
        }
        showa(a,len);
        cout<<site2(a,len)<<endl;
      
    }
    return 0;
}

上述算法则去除了一些重复计算的长度,下面我们再看一下它是如何计算子序列长度的

总长:  ------------------------------

计算中:

第一次:    j-              j=i不断变化j的初始位置

第二次:    j--              而且每次都与最长子序和比较,更新

…………:     j------------------------  

该算法虽然优化了重复计算的序列长,但其仍然复杂度仍为0(N^2),因而我们考虑其他算法进行计算

算法三(分治)O(N*log(N))

  代码如下:

 1 #include <iostream>
 2 #include <algorithm>
 3 using namespace std;
 4 int a[1000];
 5 int len;
 6 int  site3(int a[],int left,int right)
 7 {
 8     if(left==right)//初始到每一个点
 9     {   
10         return a[left];
11         /*
12         if(a[left]>0)
13         {   
14             return a[left];
15         }
16         else return 0;
17         */
18     }
19     int center=(left+right)/2;
20     int maxlefts=site3(a,left,center);
21     int maxrights=site3(a,center+1,right);//递归到每一个点,开始执行下面的计算 point1
22 
23     int leftsum=0,maxls=0;
24     for(int i=center;i>=left;i--)
25     {
26         leftsum+=a[i];
27         if(leftsum>maxls)
28             maxls=leftsum;
29     }
30    
31     int rightsum=0,maxrs=0;
32     for(int i=center+1;i<=right;i++)
33     {
34         rightsum+=a[i];
35         if(rightsum>maxrs)
36             maxrs=rightsum;
37     }
38     
39     int maxssum=maxls+maxrs;//计算中子序最大值
40    
41     int maxs=0;
42     maxs=max(maxlefts,maxrights);
43     maxs=max(maxs,maxssum);//比较得到三个子序列中最大子序和
44    
45     return maxs;
46 }
47 
48 void showa(int a[],int len)
49 {
50     for(int i=0;i<len;i++)
51     {
52         cout<<a[i]<<' ';
53     }
54     cout<<endl;
55 }
56 
57 int main()
58 {
59     while(cin>>len&&len)
60     {
61         for(int i=0;i<len;i++)
62         {
63             cin>>a[i];
64         }
65         showa(a,len);
66       
67         cout<<site3(a,0,len-1)<<endl;
68         
69     }
70     return 0;
71 }

分治法 每次计算三个序列最大子序和,进行比较,取其中最大值,即得到分段前的最大子序和复杂度为O(N*log(N))

= =分治法的主要思想就是将大问题分解成同一解法的小问题得到解,再不断合并解,从而得到最终的答案。

首先是如上面point1处,先分解到每个点

例:10

1 1 1 -5 1 2 -5 2 2 -5

我们不断分解将得到一棵形似树的数列:

1:        (1 1 1 -5 1 2 -5 2 2 -5)

2:       (1 1 1 -5 1)       (2 -5 2 2 -5)

3:    (1 1 1)      (-5 1)    (2 -5 2)     (2 -5)

4:     (1 1) (1)   (-5) (1)    (2 -5) (2)    (2) (-5)

5:      (1)  (1)              (2)  (-5) 

再求每个子数列的最大子序和,合并,得到父数列的最大子序和,不断合并,得到整个数列的最大子序和

下面还有一种更优的算法-即动态规划,但现在没时间了,下机了(╯‵□′)╯︵┻━┻回去再弄,好的现在我弄完了yeah

算法四 动态规划O(N)

 版本一:

 1 #include <iostream>
 2 #include <algorithm>
 3 using namespace std;
 4 int a[1000];
 5 int len;
 6 
 7 int  site4(int a[],int len)
 8 {
 9     int f[10000]={0};
10     for(int i=0;i<len;i++)
11     {
12         if(f[i-1]>0)
13             f[i]=f[i-1]+a[i];
14         else
15             f[i]=a[i];
16     }
17     int maxs=0;
18     for(int i=0;i<len;i++)
19     {
20         if(maxs<f[i])
21             maxs=f[i];
22     }
23     return maxs;
24 }
25 
26 void showa(int a[],int len)
27 {
28     for(int i=0;i<len;i++)
29     {
30         cout<<a[i]<<' ';
31     }
32     cout<<endl;
33 }
34 
35 int main()
36 {
37     while(cin>>len&&len)
38     {
39         for(int i=0;i<len;i++)
40         {
41             cin>>a[i];
42         }
43         showa(a,len);
44         //cout<<site(a,len)<<endl;
45         cout<<site4(a,len)<<endl;
46         //cout<<g_site(a,len)<<endl;
47     }
48     return 0;
49 }

 

  我们将每次加法分为阶段性的,那么有:

  当前阶段的子段和=上一阶段的子段和+当前元素

  即:s[i]=s[i-1]+a[i]

而大家也都应知道这么一个道理:一个数加上一个负数,那么和必然小于原来这个数,

  因而我们摒弃掉负数和,从当前点开始新的累加

  从而得到最大子段和状态转移方程:

  if(s[i-1]>0)

    s[i]=s[i-1]+a[i];

  else

    s[i]=a[i];

  最后我们计算完毕后找出其中最大值,即为最大子段和。

  

  现在,我们想一想,其实我们可以将找到最大值这个步骤放入计算的循环内

  因而,

版本二:

#include <iostream>
#include <algorithm>
using namespace std;
int a[1000];
int len;

int  site5(int a[],int len)
{
    int maxs=0,sum=0;
    for(int i=0;i<len;i++)
    {
        sum+=a[i];
        if(maxs<sum)
            maxs=sum;
        if(sum<0)
            sum=0;
    }
    return maxs;
}

void showa(int a[],int len)
{
    for(int i=0;i<len;i++)
    {
        cout<<a[i]<<' ';
    }
    cout<<endl;
}

int main()
{
    while(cin>>len&&len)
    {
        for(int i=0;i<len;i++)
        {
            cin>>a[i];
        }
        showa(a,len);
        //cout<<site(a,len)<<endl;
        cout<<site5(a,len)<<endl;
        //cout<<g_site(a,len)<<endl;
    }
    return 0;
}

相比于上面的数组记录状态,我们只用一个sum来记录状态,当为负时,我们将状态归零,从新开始计算,同时,我们也将寻求最大值放入了循环。

但这样其实舍弃了一点东西……当有多个子段和值相同时,我们仅记录了一个状态,如果要寻找其他的我们就不是很方便了。

 

以上,为本次对最大子段和的学习。

          2016.4.22

  

 

posted on 2016-04-20 16:07  八云紫是小loli  阅读(311)  评论(0编辑  收藏  举报

导航