• 最大连续子序和


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

    算法一(暴力)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

      

  • 相关阅读:
    SVN的学习
    IIS 503 错误
    Windows系统CMD下常用命令
    Linux基础整理
    JavaEESSM框架配置文件
    JavaXML整理
    Java反射、反射练习整理
    Java网络通信协议、UDP、TCP类加载整理
    Java多线程、线程池和线程安全整理
    JavaProperties类、序列化流与反序列化流、打印流、commons-IO整理
  • 原文地址:https://www.cnblogs.com/byzsxloli/p/5413165.html
Copyright © 2020-2023  润新知