4.1 The maximum-subarray problem

4.1.1 The introduction to the question

Suppose that you been offered the opportunity to invest in the Volatile Chemical Corporation. You are allowed to buy one unit of stock only one time and then sell it at a later date, buying and selling after the close of trading for the day. And you are allowed to learn what the price of the stock will be in the future.

Your goal is to maximize your profit.

You might think that you can always maximize profit by either buying at the lowest price or selling at the highest price, however, it is wrong.

4.1.2 A brute-force solution

We can easily devise a brute-force solution to this problem: just try every possible pair of buy and sell dates in which the buy date precedes the sell date.

A period of n n n days has C n 2 C_{n}^2 Cn2 such pairs of dates. Since C n 2 C^2_{n} Cn2 is Θ ( n 2 ) \Theta(n^2) Θ(n2), and the best we can hope for is to evaluate each pair of dates in constant time, this approach would take Ω ( n 2 ) \Omega(n^2) Ω(n2)

We need to do better.

4.1.3 A transformation

Instead of looking at the daily price, let us instead consider the daily change in price, where the change on day i i i is the difference between the prices after day i − 1 i-1 i1 and after day i i i.

We now want to find the nonempty, contiguous subarray of A A A whose values have the largest sum.We call this contiguous subarray themaximum subarray.

4.1.4 A solution using divide-and-conquer

Suppose we want to find a maximum subarray of the subarray A [ l o w … h i g h ] A[low \dots high] A[lowhigh]. Divide-and-conquer suggests that we divide the subarray into two subarrays of as equal size as possible. That is, we find the midpoint, say mid, of the subarray.

Then, we consider the subarrays A [ l o w … m i d ] A[low \dots mid] A[lowmid] and A [ m i d + 1 … h i g h ] A[mid+1 \dots high] A[mid+1high].Any contiguous subarray A [ i … j ] A[i \dots j] A[ij] of A [ l o w … h i g h ] A[low \dots high] A[lowhigh] must lie in exactly one of the following plcaes.

  • entirely in the subarray A [ l o w … m i d ] A[low \dots mid] A[lowmid], so that l o w ≤ i ≤ j ≤ m i d low \leq i \leq j \leq mid lowijmid
  • entirely in the subarray A [ m i d + 1 … h i g h ] A[mid+1 \dots high] A[mid+1high], so that m i d < i ≤ j ≤ h i g h mid < i \leq j \leq high mid<ijhigh
  • crossing the midpoint, so that l o w ≤ i ≤ m i d < j ≤ h i g h low \leq i \leq mid < j \leq high lowimid<jhigh

In fact, a maximum subarray of A [ l o w … h i g h ] A[low \dots high] A[lowhigh] must have the greatest sum over all subarrays entirely in A [ l o w … m i d ] A[low \dots mid] A[lowmid], entirely in A [ m i d + 1 … h i g h ] A[mid+1 \dots high] A[mid+1high] or crossing the midpoint. We can find maximum subarrays of A [ l o w … m i d ] A[low \dots mid] A[lowmid] and A [ m i d + 1 … h i g h ] A[mid+1 \dots high] A[mid+1high] recursively.

The important thing is to find a maximum subarray that crosses the midpoint, and takes a subarray with the largest sum of the three.

We can easily find a maximum subarray crossing the midpoint in time linear in the size of the subarray. This problem is not a smaller instance of our original problem,but we can convert it.

Any subarray crossing the midpoint is itself made of two subarrays A [ i … m i d ] A[i \dots mid] A[imid] and A [ m i d + 1 … j ] A[mid+1 \dots j] A[mid+1j]. Therefore, we just need to find maximum subarrays of A [ i … m i d ] A[i \dots mid] A[imid] and A [ m i d + 1 … j ] A[mid+1 \dots j] A[mid+1j] and then combine them.

The procedure FIND-MAX-CROSSING-SUBARRAY does this thing.

FIND-MAX-CROSSING-SUBARRAY(A,low,mid,high)
	left-sum = -∞
	sum = 0
	for i = mid downto low
		sum = sum + A[i]
		if sum > left-sum
			left-sum = sum
			max-left = i
	right-sum = -∞
	sum = 0
	for j = mid+1 to high
		sum = sum + A[j]
		if sum > right-sum
			right-sum = sum
			max-right = j
	return (max-left,max-right,left-sum+right-sum)

With a linear-time FIND-MAX-CROSSING-SUBARRAY procedure in hand, we can write pseudocdoe for a divide-and-conquer algorithm to solve the maximum-subarray problem:

FIND-MAXIMUM-SUBARRAY(A,low,high)
	if high == low
		return(low,high,A[low]) //base case: only one element
	else 
		mid = ⌊(low + high)/2⌋
		(left-low,left-high,left-sum) = FIND-MAXIMUM-SUBARRAY(A,low,mid)
		(right-low,right-high,right-sum) = FIND-MAXIMUM-SUBARRAY(A,mid+1,high)
		(cross-low,cross-high,cross-sum) = FIND-MAX-CROSSING-SUBARRAY(A,low,mid,high)
		if left-sum >= right-sum and left-sum >= cross-sum
			return(left-low,left-high,left-sum)
		elseif right-sum >= left-sum and right-sum >= cross-sum
			return(right-low,right-high,right-sum)
		else 
			return(cross-low,cross-high,cross-sum)
4.1.5 The C program of the algorithm
#define INF 32767
typedef struct {
    int low;
    int high;
    int sum;   
}node;
node findMaxCrossingSubarray(int A[],int low,int mid,int high) {
    int leftSum = -INF,rightSum = -INF;
    int sum = 0,maxLeft,maxRight;
    for(int i = mid; i >=low; i--) {
        sum += A[i];
        if(sum > leftSum) {
            leftSum = sum;
            maxLeft = i;
        }
    }
    sum = 0;
    for(int i = mid+1; i <= high; i++) {
        sum += A[i];
        if(sum > rightSum) {
            rightSum = sum;
            maxRight = i;
        }
    }
    node p;
    p.low = maxLeft; p.high = maxRight;
    p.sum = leftSum + rightSum;
    return p;
}
node findMaximumSubarray(int A[],int low,int high) {
    node p,left,right,cross;
    if(low == high) {
        p.low = p.high = low;
        p.sum = A[low];
        return p;
    }
    else {
        int mid = (low + high) / 2;
        left = findMaximumSubarray(A,low,mid);
        right = findMaximumSubarray(A,mid+1,high);
        cross = findMaxCrossingSubarray(A,low,mid,high);
        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 A[] ={13,-3,-25,20,-3,-16,-23,18,20,-7,12,-5,-22,15,-4,-7};
    node q;
    q = findMaximumSubarray(A,0,16);
    cout<<q.low<<endl;
    cout<<q.high<<endl;
    cout<<q.sum<<endl;
    return 0;
}

你可能感兴趣的:(算法导论)