求一数组最大字段和(最大子数组)的几种方法)

算法:[c++]求一数组最大字段和(最大子数组)的几种方法)

给定由 n n n个整数(可能为负数)组成的序列 a 1 , a 2 , a 3 , . . . , a n a_1,a_2,a_3,...,a_n a1,a2,a3,...,an,求该序列形如 ∑ k = i i a k \sum_{k=i}^ia_k k=iiak的字段和的最大值。

1.简单算法:

最直接的方法,穷举所有可能的字段,计算每段的和,找出最大的一段。

设每个段的起始位置(数组下标)为 i i i,终止位置为 j j j,求 a i + . . . + a j a_i+...+a_j ai+...+aj

int n=11;
int a[n] = {-1,2,4,-7,6,2,-3,5,-9,7,3};
int sum,max=-9999;                //max为负无穷
for(int i=0;i<n;i++){
	for(int j=0;j<n;j++){
		sum=0;
		for(int k=i;k<=j;k++){
			 sum += a[k];
			}
		if(sum>max){
			max=sum;
			}
		}
	}
cout<<"最大字段和为"<<max;

上述算法共三层循环,不难看出时间复杂度为T(n)=O(n^3)。

注意到,在计算 a i + . . . + a j + 1 a_i+...+a_{j+1} ai+...+aj+1时,可利用已经计算过的 a i + . . . + a j a_i+...+a_j ai+...+aj的值,因此可以改进此算法。

int n=11;
int a[n] = {-1,2,4,-7,6,2,-3,5,-9,7,3};
int sum,max=-9999;                           //max为负无穷
 for(int i=0;i<n;i++){                      //每循环一次,计算了所有以a[i]为起始元素的字段的值 
 	sum = 0;
 	for(int j=i;j<n;j++){                    //a[j]为当前字段结束元素 
 		sum+=a[j]; 
 		if(sum>max){
 			max=sum;
 			}
 		}
 	}
cout<<"最大字段和为"<<max;

减少了一层循环,时间复杂度为T(n)=O(n^2)。

2.分治算法(递归):

分治的方法关键在于将原问题拆分为相同子问题。

将原数组从中间拆分为两段(从中间分是为了尽可能减小递归深度,使算法更为高效),分别计算这两段的最大子段。这里所计算的子段包括了所有未跨这两个数组的子段,因此,剩下只需计算出跨数组的最大子段,并与两个子数组的最大子段相比较,选出最大的一个子段即为原数组最大子段。

例如数组 { − 1 , 2 , 4 , − 7 , 6 , 2 , − 3 , 5 , − 9 , 7 , 3 } \{-1,2,4,-7,6,2,-3,5,-9,7,3 \} {1,2,4,7,6,2,3,5,9,7,3}
分别计算子数组 { − 1 , 2 , 4 , − 7 , 6 , 2 } \{-1,2,4,-7,6,2 \} {1,2,4,7,6,2} { − 3 , 5 , − 9 , 7 , 3 } \{-3,5,-9,7,3 \} {3,5,9,7,3}的最大子段,这是子问题
跨数组的子段必然包括 2 2 2 − 3 -3 3两个元素,求最大子段,可分别向左向右依次累加。
先从2开始向左累加,
2=2, max_=2
2+6=8,max_=8
2+6-7<8,max_=8
2+6-7+4<8,max_=8
2+6-7+4+2<8,max_=8
2+6-7+4+2-1<8,max_=8
得到向左依次累加的值最大为8,向右同理。
最后考虑递归出口,即当子段只有一个元素,则返回这唯一一个元素的值。

int maxarray(int b,int e,int a[]){      //b(begin)为子段起始位置,e(end)为子段结束位置
	if(b==e)return a[b]; 

	int m1=maxarray(b,b+(e-b)/2,a);     //子问题  
	int m2=maxarray(b+(e-b)/2+1,e,a);   //子问题

	int m3=-99,t1=0;
	for(int i=0;i<=(e-b)/2;i++){        //左累加
		t1=t1+a[b+(e-b)/2-i];
		if(t1>m3)m3=t1;
	}
	int m4=-99,t2=0;
	for(int i=0;i<=e-(e-b)/2-1-b;i++){  //右累加
		t2=t2+a[b+(e-b)/2+1+i];
		if(t2>m4)m4=t2;
	}
	int m5=m4+m3;              

	if(m1>=m2&&m1>=m5)return m1;    
	else if(m2>=m1&&m2>=m5)return m2;
	else return m5;                    //比较得到最大子段
}

int main(){
int n=11;
int a[n] = {-1,2,4,-7,6,2,-3,5,-9,7,3};
cout<<"最大字段和为"<<maxarray(0,10,a);
return 0;
}

由以上算法可以看出时间复杂度 T ( n ) T(n) T(n)包括两个 T ( n / 2 ) T(n/2) T(n/2)的子问题和两次次数为 n / 2 n/2 n/2的循环,递归出口的时间复杂度为 1 1 1,因此可得到如下表达式:

T ( n ) = { 1 n = 1 T ( n / 2 ) + O ( n ) n > 1 T(n)=\left\{\begin{array}{rcl}1 & n=1 \\T(n/2)+O(n) & n>1 \end{array}\right. T(n)={1T(n/2)+O(n)n=1n>1

可知时间复杂度 T ( n ) = O ( n   l o g ) T(n)=O(n \ log) T(n)=O(n log)

3.动态规划

首先观察数组,如 { − 1 , 2 , 4 , − 7 , 6 , 2 , − 3 , 5 , − 9 , 7 , 1 } \{-1,2,4,-7,6,2,-3,5,-9,7,1 \} {1,2,4,7,6,2,3,5,9,7,1}
它的最大子段为 { 6 , 2 , − 3 , 5 } \{6,2,-3,5\} {6,2,3,5},之所以最大,是因为该子段无论向左或者是向右添加元素,都会出现负增长,如向右合并子段 { − 9 , 7 , 1 } \{-9,7,1 \} {9,7,1},和值 − 1 -1 1

因此可以构造这样一种算法:自底(或自顶)依次增加元素,并计算子段和,用一个新数组记录每次增加一个元素后的新子段和,当新子段和为负数时,其他段若合并该段一定会出现负增长,因此将该段舍去,以下一个正数为起点重新计算段和,用max记录整个过程中计算的最大值和即为原数组最大子段和。

如数组 { − 1 , 2 , 4 , − 7 , 6 , 2 , − 3 , 5 , − 9 , 7 , 1 } \{-1,2,4,-7,6,2,-3,5,-9,7,1 \} {1,2,4,7,6,2,3,5,9,7,1},设记录子段和的新数组为 s s s,这里用自底增加:

子段子段和最大子段和
{ 1 } \{1 \} {1} s [ 10 ] = 1 s[10]=1 s[10]=1max =1
{ 7 , 1 } \{7,1 \} {7,1} s [ 9 ] = 8 s[9]=8 s[9]=8max = 8
{ − 9 , 7 , 1 } \{-9,7,1 \} {9,7,1} s [ 8 ] = − 1 s[8]=-1 s[8]=1(该段舍去)max = 8
{ 5 } \{5 \} {5} s [ 7 ] = 5 s[7]=5 s[7]=5max = 8
{ − 3 , 5 } \{-3,5 \} {3,5} s [ 6 ] = 2 s[6]=2 s[6]=2max = 8
{ 2 , − 3 , 5 } \{2,-3,5 \} {23,5} s [ , 5 ] = 4 s[,5]=4 s[,5]=4max = 8
{ 6 , 2 , − 3 , 5 } \{6,2,-3,5 \} {6,2,3,5} s [ 4 ] = 10 s[4]=10 s[4]=10max = 10
{ − 7 , 6 , 2 , − 3 , 5 } \{-7,6,2,-3,5 \} {7,6,2,3,5} s [ 3 ] = 3 s[3]=3 s[3]=3max = 10
{ 4 , − 7 , 6 , 2 , − 3 , 5 } \{4,-7,6,2,-3,5 \} {4,7,6,2,3,5} s [ 3 ] = 7 s[3]=7 s[3]=7max = 10
{ 2 , 4 , − 7 , 6 , 2 , − 3 , 5 } \{2,4,-7,6,2,-3,5 \} {2,4,7,6,2,3,5} s [ 3 ] = 9 s[3]=9 s[3]=9max = 10
{ − 1 , 2 , 4 , − 7 , 6 , 2 , − 3 , 5 } \{-1,2,4,-7,6,2,-3,5 \} {1,2,4,7,6,2,3,5} s [ 3 ] = 8 s[3]=8 s[3]=8max = 10

代码:

int n=11,max=-9999;
	int a[n]={-1,2,4,-7,6,2,-3,5,-9,7,1};
	int s[n];
	
	s[n-1]=a[n-1];             //自底开始                          
	
	for(int i=1;i<n;i++){
	
		if(s[n-i]>0)s[n-i-1]=s[n-i]+a[n-i-1];      
		else s[n-i-1]=a[n-i-1];               //大于0累加,小于0舍去
		
		if(s[n-i-1]>max)max=s[n-i-1];     
	}
	
	cout<<"最大字段和为"<<max;	

不难看出,只有一层循环,时间复杂度为 T ( n ) = O ( n ) T(n)=O(n) T(n)=O(n)

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值