zoukankan      html  css  js  c++  java

最大连续子向量和

问题描写叙述:

输入是具有n个浮点数的向量x,输出这个向量的不论什么连续子向量中的最大和。

简单分析:子向量能够是一个空向量,空向量的和为0;假设向量的全部元素都是负数,最大子向量和就是0;

1 简单分析后,对于这个问题,我们立刻能向想到的就是暴力算法,对全部0<=i<=j<n的整数对进行迭代。对每一个整数对(i,j),程序都要计算x[i…j]的总和,并推断其是否大于当前的最大总和。

解法1:简单粗暴型
int res=0; //答案
for(int i= 0 ; i<n;i++)
    for(int j=i;j<n;j++)
    {
        sum=0
        for(int k=i;k<=j;k++) 
            sum+=x[k]
        res=max(res,sum)    
    }

2 怎么看,上面的算法都是简单粗暴型,O(n^3)的时间复杂度实在不敢恭维,数据量一大,时间上实在不能容忍。那么有没有略微优雅一点的?我们发现后面的部分有反复计算的,那么我们怎样节省它~~一种就是从i開始往前加的时候,每次都记录下来。直接看代码:

解法2int res=0;  //答案
for(int i=0;i<n;i++)
{
    int sum=0;  
    for(int j=i;j<n;j++)
    {
        sum+=x[j];  // sum 就是 x[i]至x[j]的和 
        res=max(res,sum);  
    }
}

3 上面的代码,尽管比简单粗暴型有一些改进,算法的时间复杂度降为O(n^2),另一种O(n^2)的算法,令sum(i)表示x[0…i]的总和,然后,x[i] = sum(i) - sum(i-1);

解法3sum[-1]=0
for i=[0,n)
    sum[i]=sum[i-1]+x[i]
res=0  //储存答案
for i=[0,n)
    for j=[i,n)
        tem=sum[j]-sum[i-1] 
        res=max(res,tem)

4 O(n^2)的效率,我们还是认为不行,可不能够优化一下,好了,我们能够採用分治的思想。要解决规模为n的问题,可递归地解决两个规模近似为n/2的子问题,然后对两个结果进行合并以得到整个问题的答案。将x划分为两个近似相等的子向量ab,在a和b中分别找出总和最大的子向量ma和mb,然后找到跨越a和b边界的最大子向量mc,返回三个总和中的最大者。通过观察发现mc在a中的部分是a中包括右边界的最大子向量,mc在b中的部分是b中包括左边界的最大子向量。伪代码例如以下:

解法4:
float maxsum(l,u)
    if(l>u)  return 0  /* zero elements */
    if(l==u)  return max(0,x[1])  /* one element */
    m=(l+u)/2
    lmax=sum=0;
    for(i=m;i>=l;i--)  /* find max crossing to left */
        sum+=x[i]
        lmax=max(lmax,sum)
    rmax=sum=0
    for i=(m,u]  /* find max crossing to right */
        sum+=x[i]
        rmax=max(rmax,sum)
    return  max(lmax+rmax,maxsum(l,m),maxsum(m+1,u))

5 能否够再优化一下?好了,事实上能够。使用扫描算法:我们採用从x[0]開始扫描,一起到最右端x[n-1],并记下所遇到的最大子向量总和(初始值设为0)。如果我们已攻克了x[0,i-1]的问题,怎样将其扩展到x[0…i]呢?前i个元素中,最大总和子数组要么在前i-1个元素中(用maxsofar存储),要么其结束位置为i(用maxendinghere存储)。

maxsofar=0
maxendinghere=0
for i=[0,n)
    maxendinghere=max(maxendinghere+x[i],0) /* 计算前maxendinghere是结束位置为i-1的最大子向量的和 */
    maxsofar=max(maxsofar,maxendinghere)

几个重要的算法设计技术:

  • 保存状态,避免反复计算:

  • 将信息预处理至数据结构:   

  • 分治算法:

子向量和接近于0

如果我们想要查找的是总和最接近0的子向量,而不是具有最大总和的子向量,该怎样设计算法?

可初始化累加数组cum,使得cum[i]=x[0]+…+x[i]。假设cum[l-1]=cum[u],那么子向量x[l…u]之和就为0.因此能够通过定位cum中最接近的两个元素来找出和最接近0的子向量。这能够通过排序数组,在O(nlogn)时间内完毕。


收费站问题

问题描写叙述:

收费公路由n个收费站之间的n-1段公路组成,每一段都和行驶费用挂钩,仅使用费用数组依照O(n)的时间,或者使用具有O(n^2)个项的表依照O(1)的时间描写叙述随意两站之间的费用是无意义的,请设计一个结构,它须要O(n)的空间,但它同意O(1)的时间复杂度求解。

驶过两个收费站,就是一段公路,汽车在行驶时,仅仅能连续行驶,不会从这段公路跳到后面的公路。所以就符合连续子向量的求和问题。

可初始化累加数组cum,使得cum[i]=x[0]+…+x[i],

对于收费站i和j,cum[j] - cum[i-1]就表示在i和j内行驶的路段费用,而且仅仅占用 cum[n]的线性空间。


区间赋值问题

对数组array[0…n-1]初始化为全0后,运行n次运算:for i = [l,u] {x[i] += v;},当中l,u,v是每次运算的參数,0<=l<=u<=n-1。直接用这个伪码须要O(n2)的时间,请给出更快的算法。

初始化y[0,…,n-1]为全0,对每一个操作令y[l]+=v和y[u+1]-=v。则结束时x[i]=sigma{k=0 to i}(y[k])。正确性:仅仅需证明每一次运行完操作之后该性质保持不变就可以。注意这里的y[i]表示的意义


二维连续子数组和

二维数组连续的二维子数组的和怎么求,肯定是一个矩形,我们要遍历吗???
遍历的话预计复杂度扛不住吧。。怎样遍历也是一个问题。

这个时候我们能够把每一行看成是一个元素,这样就变成了一个纵向的一维数组了。

这样对一维数组的遍历是和刚才一样的。而对于每一行我们遍历也是和一维是一样的。编码试一试

 //求二维数组的连续子数组之和的最大值
  int MaxSum(int (*array)[N])
  {
      int i,j;
      int MaxSum=-INFINITY;//初始化
      int imin,imax,jmin,jmax;
      for(imin=1;imin<=N;imin++)
  {
          for(imax=imin;imax<=N;imax++)//当成是遍历纵向的一维
  {
              for(jmin=1;jmin<=M;jmin++)
  {
                  for(jmax=jmin;jmax<=M;jmax++)//当成是遍历横向的一维
                          MaxSum=MaxNum(MaxSum,PartSum(imin,jmin,imax,jmax));
              }
  }
  }            
      return MaxSum;
  }
  

时间复杂度(N^2M^2O(PartSum)),怎样求部分和PartSum呢?假设这个还是要遍历求的话,复杂度真是不敢看了。。
求一维的时候我们求Sum[i-j]非常好求,但是求二维的时候就变成了四个坐标了,不敢遍历求和了。我们能够先求部分和,把他当作已知的,这个时候遍历求的时候复杂度就是O(1)。
怎样求?我们定义一个部分和数组PartSum,当中PartSum[i][[j]代表了下标(0,0),(0,j),(i,0),(i,j)包围的区间的和。

而此时下标(imin,jmin),(imin,jmax),(imax,jmin),(imax,jmax)包围的区间和就等于

PartSum[imax][[jmax]-PartSum[imin-1][[jmax]-PartSum[imax][[jmin-1]+PartSum[imin-1][[jmin-1]。

这就是我们要求的PartSum(imin,jmin,imax,jmax),接下来就是求PartSum数组了。怎样求呢?
对于每个PartSum[i][[j]都不是孤立的,都是和其它的有关系的。我们要找出这个关系式

PartSum[i][[j]=PartSum[i-1][[j]+PartSum[i][[j-1]-PartSum[i-1][[j-1]+array[i][j]。

这样求能够求出所有的PartSum[i][[j],但是我们不要忽略了一点,PartSum[0][[0]=?对于边界值我们要处理好,并且下标要从1開始。对于PartSum[i][[0]和PartSum[0][[j]都要初始化0,并且array[i][j]的下标也是要-1,由于数组的下标是从0開始的。这是一个办法,只是我们也能够单独求PartSum[i][[0]和PartSum[0][[j]的值,连续相加就可以,然后再求其它的也是能够的,空间复杂度也是一样。但是在4重遍历的时候对于PartSum[i][[0]和PartSum[0][[j]我们还是要另外处理,这就比較麻烦了。我们还是用预处理的方法来编码吧。。

    int PartSum[N+1][M+1];
      int i,j;
      for(i=0;i<=N;i++)
          PartSum[i][0]=0;
      for(j=0;j<=M;j++)
          PartSum[0][j]=0;
      for(i=1;i<=N;i++)
          for(j=1;j<=M;j++)
          PartSum[i][j]=PartSum[i-1][j]+PartSum[i][j-1]-PartSum[i-1][j-1]+array[i-1][j-1];

OK,求得部分和之后我们就開始完好我们的编码了。记住一点,下标(imin,jmin),(imin,jmax),(imax,jmin),(imax,jmax)包围的区间和等于
PartSum[imax][[jmax]-PartSum[imin-1][[jmax]-PartSum[imax][[jmin-1]+PartSum[imin-1][[jmin-1]。
编码開始:

//求二维数组的连续子数组之和的最大值
int MaxSum(int (*array)[N])
{
    int PartSum[N+1][M+1];
    int i,j;
    for(i=0;i<=N;i++)
        PartSum[i][0]=0;
    for(j=0;j<=M;j++)
        PartSum[0][j]=0;
    for(i=1;i<=N;i++)
        for(j=1;j<=M;j++)
            PartSum[i][j]=PartSum[i-1][j]+PartSum[i][j-1]-PartSum[i-1][j-1]+array[i-1][j-1];
    int MaxSum=-INFINITY;//初始化
    int imin,imax,jmin,jmax;
    for(imin=1;imin<=N;imin++)
        for(imax=imin;imax<=N;imax++)
            for(jmin=1;jmin<=M;jmin++)
                for(jmax=jmin;jmax<=M;jmax++)
                        MaxSum=MaxNum(MaxSum,PartSum[imax][jmax]-PartSum[imin-1][jmax]-PartSum[imax][jmin-1]+PartSum[imin-1][jmin-1]);

    return MaxSum;
}

时间复杂度是O(N^2*M^2),有点坑啊。想一想一维的时候我们用DP来做,这个也能够吗?能够的。我们把每一列看成一个元素。这样对于遍历的行区间,我们就能够当成一维来做。

对于imin和imax之间的每一列,就相当于一维的一个元素。

如果这个一维数组是BC,则BC[j]=array[imin][j]+….+array[imax][j],问题就变成了求BC数组的连续子数组之和的最大值了。而依据刚才求的部分和,我们能够知道对于imin行和imax行之间的区间第j列的值是

BC(PartSum,imin,imax,j)=PartSum[imax][j]-PartSum[imin-1][j]-PartSum[imax][j-1]+PartSum[imin-1][j-1].(此时BC是一个函数)
OK,编码開始

//求二维数组的连续子数组之和的最大值
int MaxSum(int (*array)[N])
{
    int PartSum[N+1][M+1];
    int i,j;
    for(i=0;i<=N;i++)
        PartSum[i][0]=0;
    for(j=0;j<=M;j++)
        PartSum[0][j]=0;
    for(i=1;i<=N;i++)
        for(j=1;j<=M;j++)
            PartSum[i][j]=PartSum[i-1][j]+PartSum[i][j-1]-PartSum[i-1][j-1]+array[i-1][j-1];
    int MaxSum=-INFINITY;
    int Start,All;
    int imin,imax;
    for(imin=1;imin<=N;imin++)
    {
        for(imax=imin;imax<=N;imax++)
        {
            Start=BC(PartSum,imin,imax,M);
            All=BC(PartSum,imin,imax,M);
            for(j=M-1;j>=1;j--)
            {
                if(Start>0)
                    Start+=BC(PartSum,imin,imax,j);
                else
                    Start=BC(PartSum,imin,imax,j);
                if(Start>All)
                    All=Start;
            }
            if(All>MaxSum)
                MaxSum=All;
        }
    }
    return MaxSum;
}

int BC(int (*PartSum)[N+1],int imin,int imax,int j) //imin--imax第j列的和
{
    int value;
    value=PartSum[imax][j]-PartSum[imin-1][j]-PartSum[imax][j-1]+PartSum[imin-1][j-1];
    return value;
}

时间辅助度降到O(NMmin(M,N)),差点儿相同O(N^3)吧。


參考资料:

查看全文
  • 相关阅读:
    C语言:运算结果保留两位小数
    FFmpeg: AVFrame中的data和extend_data的区别
    android studio: 配置版权信息(转)
    C++: C++11 成员函数作为pthread线程 (转)
    android studio: 取消行注释在第一列
    C 语言 int 读写是否需要加锁
    【转】浅析Linux中的零拷贝技术--简单形象
    【转】Linux 内核态与用户态--简明清晰的介绍
    delete如何找到多重继承对象的内存块起始地址
    【转】内存管理内幕mallco及free函数实现--简易内存分配器、内存池、GC技术
  • 原文地址:https://www.cnblogs.com/blfshiye/p/4084948.html
  • Copyright © 2011-2022 走看看