您的位置:首页 > 其它

求最大连续子序列和4种算法解析

2015-04-16 23:29 211 查看
1. 题目描述:

给出一个长度为N的序列:a1,a2,……,an,求最大连续和。找到1=<I<=J<=N,使得ai+a(i+1)+……+aj尽量大。(若最大连续和为负数,保留之,不必替换为0。)

2. 算法1

1) 思路解析:枚举所有可能的子序列的和,通过三重循环每次求出一个数组下标范围为[index1, index2]的子序列的和,每次求出和后进行判断,更新最大值。

2) 时间复杂度O(N^3);空间复杂度O(1)

代码:

void ON3(int test[],int arraysize)
{
   int thissum,maxsum=-INF;//初始化最大值为负无穷
   for(int index1=0; index1<arraysize; index1++)//枚举子序列的起点下标
    {
       for(int index2=index1; index2<arraysize; index2++)//枚举子序列的终点下标
       {
           thissum=0;
           for(int index3=index1; index3<=index2; index3++)//求[index1,index2]子序列的和
           {
                thissum=thissum+test[index3];
           }
           maxsum=max(maxsum,thissum);// 更新最大值
       }
    }
   cout<<"最大连续和为:"<<maxsum<<endl;
}


3. 算法2

1) 思路解析:基本同算法1,改进在于去掉第三层循环,第二层循环枚举子序列的终点下标时,每次枚举都进行依次累加,thissum不在第二层循环内清空,减少了部分重复的计算。

2) 时间复杂度O(N^2);空间复杂度O(1).

代码:

void ON2(int test[],int arraysize)
{
   int thissum,maxsum=-INF;
   for(int index1=0; index1<arraysize; index1++)
    {
       thissum=0;
       for(int index2=index1; index2<arraysize; index2++)
       {
           thissum=thissum+test[index2];
           maxsum=max(maxsum,thissum);
       }
    }
   cout<<"最大连续和为:"<<maxsum<<endl;
}


4. 算法3

1) 思路解析:采取分治法。我们从满足结果的子序列的特性进行分析。假如把序列分成左半部分和右半部分,那么最大子序列和可能在三处出现。或者是整个出现在左半部分,或者是整个出现在右半部分,或者是横跨左右两部分。对于前两种情况可以递归求解,对于第三种情况,可以先求出左半部分的最大和(包含左半部分的最后一个元素)以及右半部分的最大和(包含右半部分的第一个元素),然后再把两个和相加得到。最后取这三个最大值中的最大值即可。主程序中调用ONlogN(testarray
, 0 , arraysize-1)即可。

2) 时间复杂度O(NlogN);空间复杂度O(1).时间复杂度分析:

设T(N)为求解大小为N的最大子序列和问题所花费的时间。显然T(1)=1;否则,程序必须得运行两次递归(每个递归的程序的运行时间为T(N/2),共2* T(N/2)),后续的两个for循环将当前的数组扫描一遍,用时O(N),故N>1时,T(N)=2*T(N/2)+O(N)。结合以上两个方程T(1)=1;T(N)=2*T(N/2)+O(N)。可以推出:时间复杂度为O(NlogN)。

证明过程如下:

为方便推导,假设n=2^k(若n不为2的幂,大O的结果是不变的。),

T(n) = 2T(n / 2) + O(n)

= (4T(n / 4) + 2O(n/2)) + O(n)

= ((8T(n / 8) + 4O(n/4)) + O(n)) + O(n)

=…

= 2^kT(1) + O(n) + … + O(n) + O(n) + O(n)

= nO(1) + k * O(n) = (k + 1)O(n) = O(n *log2n)

代码:

int ONlogN(int test[],int left,int right)
{
   int maxleftsum,maxrightsum;//子问题的最大值
   int maxleftbordersum=-INF,maxrightbordersum=-INF;//左半和右半部分各自的最大值
   int leftbordersum=0,rightbordersum=0;
    if(left==right)//当子区间只有一个数的时候返回这个数
    {
       return test[left];
    }
int middle=left+(right-left)/2;
//求中点的下标,整数除法是去掉小数部分的,所以中点的值要么在正中间,
//要么偏向left,永不会偏向right,即left永远不会大于right,
//当相等时不会递归调用。(right+left)/2有可能数据溢出。
   maxleftsum=ONlogN(test,left,middle);//分治
   maxrightsum=ONlogN(test,middle+1,right);
   for(intindex=middle;index>=left;index--)//求左半部分最大连续和,middle为起点递减
    {
       leftbordersum=leftbordersum+test[index];
       maxleftbordersum=max(maxleftbordersum,leftbordersum);
    }
for(intindex=middle+1;index<=right;index++)
//求右半部分的最大连续和,middle+1为起点递增
    {
       rightbordersum=rightbordersum+test[index];
       maxrightbordersum=max(maxrightbordersum,rightbordersum);
    }
returnmax(maxleftsum,max(maxrightsum,maxleftbordersum+maxrightbordersum));
//取三者的最大值返回
}


5. 算法4

1) 思路解析:明确整个数组的最大连续子序列和必定为某个数组元素所在的最大连续子序列的和。对核心代码的解析:

void ON(int test[],int arraysize)
{
    int maxsum=-INF,thissum=0;//thissum表示当前元素所在的最大子序列的和
    for(int index=0; index<arraysize;index++)
    {
        if(thissum<=0)
        {
            thissum=test[index];
//若当前元素前面的最大连续子序列和为非正,那么重新开始
        }
        else
        {
            thissum=thissum+test[index];//否则累加上当前元素
        }
                            //至此求出了对当前元素而言的最大连续和
        maxsum=max(maxsum,thissum);//每次求出一个和后,更新最大值
    }
    cout<<"最大连续和为:"<<maxsum<<endl;
}


2) 时间复杂度为:O(N),空间复杂度为:O(1)

3) 联机算法:在任意时刻算法对要操作的数据只读入(扫描)一次,一旦被读入并处理,它就不需要在被记忆了。而在此处理过程中算法能对它已经读入的数据立即给出相应子序列问题的正确答案。具有这种特性的算法叫做联机算法(on-line algorithm)。该算法仅需要常量空间并以线性时间运行,因此联机算法几乎是完美的算法。优点:占用空间少,所用时间少;缺点:不宜设计,正确性不易观察,同时附加保留信息较少。该算法即为联机算法的代表。

代码:

void ON(int test[],int arraysize)
{
   int maxsum=-INF,thissum=0;
   for(int index=0; index<arraysize; index++)
    {
       if(thissum<=0)
       {
           thissum=test[index];
       }
       else
       {
           thissum=thissum+test[index];//否则累加上当前元素
       }
       maxsum=max(maxsum,thissum);
    }
   cout<<"最大连续和为:"<<maxsum<<endl;
}


6. 测试程序:

#include<iostream>
#include<algorithm>
using namespace std;
 
const int INF=2000000000;
int test[10010];
 
void ON3(int test[],int arraysize);
void ON2(int test[],int arraysize);
int ONlogN(int test[],int left,int right);
void ON(int test[],int arraysize);
 
int main()
{
   int arraysize;
 
   while(cin>>arraysize&&arraysize)
    {
       for(int i=0; i<arraysize; i++)
       {
           cin>>test[i];
       }
 
       ON3(test,arraysize);
 
       ON2(test,arraysize);
 
       cout<<"最大连续和为:"<<ONlogN(test,0,arraysize-1)<<endl;
 
       ON(test,arraysize);
    }
 
   return 0;
}
 
void ON3(int test[],int arraysize)
{
   int thissum,maxsum=-INF;
   for(int index1=0; index1<arraysize; index1++)
    {
       for(int index2=index1; index2<arraysize; index2++)
       {
           thissum=0;
           for(int index3=index1; index3<=index2; index3++)
           {
                thissum=thissum+test[index3];
           }
 
           maxsum=max(maxsum,thissum);
       }
    }
   cout<<"最大连续和为:"<<maxsum<<endl;
}
 
void ON2(int test[],int arraysize)
{
   int thissum,maxsum=-INF;
   for(int index1=0; index1<arraysize; index1++)
    {
       thissum=0;
       for(int index2=index1;index2<arraysize; index2++)
       {
           thissum=thissum+test[index2];
           maxsum=max(maxsum,thissum);
       }
    }
 
   cout<<"最大连续和为:"<<maxsum<<endl;
}
 
int ONlogN(int test[],int left,int right)
{
   int maxleftsum,maxrightsum;
   int maxleftbordersum=-INF,maxrightbordersum=-INF;
   int leftbordersum=0,rightbordersum=0;
 
   if(left==right)
    {
       return test[left];
    }
 
   int middle=left+(right-left)/2;
 
   maxleftsum=ONlogN(test,left,middle);
   maxrightsum=ONlogN(test,middle+1,right);
 
   for(int index=middle; index>=left; index--)
    {
       leftbordersum=leftbordersum+test[index];
       maxleftbordersum=max(maxleftbordersum,leftbordersum);
    }
 
   for(int index=middle+1; index<=right; index++)
    {
       rightbordersum=rightbordersum+test[index];
       maxrightbordersum=max(maxrightbordersum,rightbordersum);
    }
 
   returnmax(maxleftsum,max(maxrightsum,maxleftbordersum+maxrightbordersum));
}
 
void ON(int test[],int arraysize)
{
   int maxsum=-INF,thissum=0;
   for(int index=0; index<arraysize; index++)
    {
       if(thissum<0)
       {
           thissum=test[index];
       }
       else
       {
           thissum=thissum+test[index];
       }
       maxsum=max(maxsum,thissum);
    }
 
   cout<<"最大连续和为:"<<maxsum<<endl;
}


运行示例:

6

-2 11 -4 13 -5 -2

最大连续和为:20

最大连续和为:20

最大连续和为:20

最大连续和为:20

10

-10 1 2 3 4 -5 -23 3 7 -21

最大连续和为:10

最大连续和为:10

最大连续和为:10

最大连续和为:10

6

5 -8 3 2 5 0

最大连续和为:10

最大连续和为:10

最大连续和为:10

最大连续和为:10

1

10

最大连续和为:10

最大连续和为:10

最大连续和为:10

最大连续和为:10

3

-1 -5 -2

最大连续和为:-1

最大连续和为:-1

最大连续和为:-1

最大连续和为:-1

3

-1 0 -2

最大连续和为:0

最大连续和为:0

最大连续和为:0

最大连续和为:0

0



Process returned 0 (0x0) execution time : 3.391 s

Press any key to continue.

7. HDU1231:最大连续子序列

Problem Description

给定K个整数的序列{ N1, N2, ..., NK },其任意连续子序列可表示为{ Ni, Ni+1, ..., Nj },其中 1 <= i <= j<= K。最大连续子序列是所有连续子序列中元素和最大的一个, 例如给定序列{ -2, 11, -4, 13, -5, -2 },其最大连续子序列为{ 11, -4,13 },最大和 为20。 在今年的数据结构考卷中,要求编写程序得到最大和,现在增加一个要求,即还需要输出该子序列的第一个和最后一个元素。

Input

测试输入包含若干测试用例,每个测试用例占2行,第1行给出正整数K( < 10000 ),第2行给出K个整数,中间用空格分隔。当K为0时,输入结束,该用例不被处理。

Output

对每个测试用例,在1行里输出最大和、最大连续子序列的第一个和最后一个元

素,中间用空格分隔。如果最大连续子序列不唯一,则输出序号i和j最小的那个(如输入样例的第2、3组)。若所有K个元素都是负数,则定义其最大和为0,输出整个序列的首尾元素。

Sample Input

6

-2 11 -4 13 -5 -2

10

-10 1 2 3 4 -5 -23 3 7 -21

6

5 -8 3 2 5 0

1

10

3

-1 -5 -2

3

-1 0 -2

0

Sample Output

20 11 13

10 1 4

10 3 5

10 10 10

0 -1 -2

0 0 0

Hint

Huge input, scanf is recommended.

已经AC的代码:

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
 
const int INF=2000000000;
int test[10010];
 
void ON(int test[],int arraysize);
 
int main()
{
   int arraysize;
   while(scanf("%d",&arraysize)&&arraysize)
    {
       for(int i=0; i<arraysize; i++)
       {
           scanf("%d",&test[i]);
        }
       ON(test,arraysize);
    }
   return 0;
}
 
void ON(int test[],int arraysize)
{
   int maxsum=-INF,thissum=0;
   int left=test[0],right=test[0];//初始化左端点和右端点
   int flag;//用于更新左端点
   for(int index=0; index<arraysize; index++)
    {
       if(thissum<=0)
       {
           thissum=test[index];
           flag=test[index];//左端点更新
       }
       else
       {
           thissum=thissum+test[index];
       }
       if(thissum>maxsum)
       {
           maxsum=thissum;
            left=flag;
           right=test[index];//右端点始终为当前的元素
       }
    }
   if(maxsum<0)
    {
       cout<<"0 "<<test[0]<<""<<test[arraysize-1]<<endl;
    }
   else
    {
       cout<<maxsum<<" "<<left<<""<<right<<endl;
    }
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: