分治-最大子數組

問題描述:

給定一個數組,從中尋找最大子數組(最大子數組:子數組內元素之和最大)。顯然此問題僅在數組中包含正負值下才有效,否則,最大子數組是其本身。

問題分析:

設原數組爲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)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章