分治-最大子数组

问题描述:

给定一个数组,从中寻找最大子数组(最大子数组:子数组内元素之和最大)。显然此问题仅在数组中包含正负值下才有效,否则,最大子数组是其本身。

问题分析:

设原数组为A[0……len-1],子数组中任意元素为B[i]。则i-1,i-2,……0,i+1,i+2……len-1,皆有可能是子数组内元素。因此我们可以将数组划分为一个个单一元素,然后再通过合并确定子数组的边界情况。

问题求解:
暴力求解:

遍历数组的所有子集情况,最终找到的答案毋庸置疑,但是性能堪忧(n^2)。

分治求解:

先来分析下子数组的位置情况。
首先将数组一分为二(中间位置为mid),则子数组位置不外乎以下三种情况:

  • 在mid左侧
  • 在mid右侧
  • 横跨mid

毫无疑问,此性质在每次数组二分之后(划分为原数组的一半)仍满足。
左右两侧情况相较容易处理,但横跨mid情况比较特殊(无法分解求解)。因此我们可以单独处理此种情况。既然左右两侧均是子数组的子集,则我们可以每次从mid开始向左和右遍历。以向左为例:从mid开始遍历,设此时最大子数组为A[mid]。则最大子数组要么为A[mid],要么为A[mid, mid-1,……mid-i](i=1, 2, ……,但i不能小于左侧边界)。向右遍历与此情况相同,但避免mid被重复计算,则应从mid+1开始遍历。
代码示例:

#include<iostream>
using namespace std;

#define INT -1e7
struct Data{
 	int lo, hi;
 	int sum;
}; 

Data maxCrossingSubarray(int *A, int lo, int mi, int hi){
 	int leftSum = INT, rightSum = INT;
 	int left, right;
 	int sum = 0;
 	for(int i = mi; i >= lo; i--){
  		sum += A[i];
  		if(sum > leftSum){
   			leftSum = sum;
   			left = i;
  		}
 	}
 	sum = 0;
 	for(int i = mi+1; i <= hi; i++){
  		sum += A[i];
  		if(sum > rightSum){
   			rightSum = sum;
   			right = i;
  		}
 	}
 	Data temp;
 	temp.lo = left;
 	temp.hi = right;
 	temp.sum = leftSum + rightSum;
 	return temp;
}
Data maxSubarray(int *A, int lo, int hi){
 	Data temp, left, right, cross;
 	if(lo == hi){
  		temp.hi = temp.lo = lo;
  		temp.sum = A[lo];
  		return temp;
 	}
 	int mi = (lo + hi) / 2;
 	left = maxSubarray(A, lo, mi);
 	right = maxSubarray(A, mi+1, hi);
 	cross = maxCrossingSubarray(A, lo, mi, hi);
 	if(left.sum >= right.sum && left.sum >= cross.sum)
  		return left;
 	else if(right.sum >= left.sum && right.sum >= cross.sum)
  		return right;
 	else return cross;
}

int main(){
 	int n;
 	cin >> n;
 	int A[n];
 	for(int i = 0; i < n; i++){
  		cin >> A[i];
 	}
	Data temp = maxSubarray(A, 0, n-1);
 	cout << temp.lo << " " << temp.hi << " " << temp.sum;
 	return 0;
} 

时间杂度分析:
maxSubarray()函数递归调用同二分算法相同,最多经过logn此递归到达递归基,并且有n个元素需要合并,因此时间复杂度为O(nlogn)。

线性复杂度求解:

若A[0……i]最大子数组为B,则A[0……i+1]的最大子数组要么为B要么为A[0……i+1]内的任意一段。有了这个思路则可以自左至右遍历整个数组,每扫描一个元素则按上述性质比较。
代码示例:

#include<iostream>
using namespace std;

struct Data{
 	int lo, hi;
 	int sum;
}; 

Data initData(Data t, int n){
 	t.sum = n;
 	t.lo = 0;
 	t.hi = 0;
 	return t;
}
Data maxSubarray(int *A, int len){
 	Data t, s;
 	t = initData(t, A[0]);
 	s = initData(s, A[0]);
	for(int i = 1; i < len; i++){
  		s.sum += A[i];
  		if(s.sum >= 0){
   			s.hi = i;
   			if(s.sum > t.sum){
    				t.sum = s.sum;
   			 	t.hi = s.hi;
    				t.lo = s.lo;
   			}
  		} else{
   			s.sum = 0;
   			s.lo = i+1;
  		}  
 	} 
 	return t;
}

int main(){
 	int n[] = {67, 0, 24, -58, -64, 45, 27, 91};
 	Data temp = maxSubarray(n, 8);
 	cout << temp.lo << " " << temp.hi << " " << temp.sum;
 	return 0;
}

此种算法只需要遍历一遍整个数组便可以求解问题,因此时间复杂度为O(n)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值