在分治策略中,我们递归地求解一个问题,在每层递归中应用如下三个步骤:
分解步骤将问题划分为一些子问题,子问题的形式与原问题一样,只是规模更少。
解决步骤递归地求解出子问题。如果子问题的规模足够小,则停止递归,直接求解。
合并步骤将子问题的解组合成原问题的解。
最大子数组问题
问题描述:某家公司的股票价格是不稳定的,你可以在某个时刻买进一股该公司的股票,并在之后将其卖出,买进卖出都是在当天交易结束后进行。你知道股票的将来价格。你的目标是最大化收益。即“低价买进,高价卖出”。
股票价格:A[] = {100,113,110,85,105,102,86,63,81,101,94,106,101,79,94,90,97};
暴力求解方法
可以直接选择两个日期的股票价格组合,有n(n-1)/2可能的组合,其时间复杂度为n^2。
分治策略
我们的目的是寻找一段日期,使得从第一天到最后一天的股票价格净变值最大。我们可以不从每日价格的角度去看待输入数据,而是考察每日价格变化,第i天的价格变化定义为第i天和第i-1天的价格差,故可以得到价格变化:
价格变化 B[] = {13,-3,-25,20,-3,-16,-23,18,20,-7,12,-5,-22,15,-4,7};
那么问题就转化为寻找B的和最大的非空连续子数组,即最大子数组。
使用分治策略求解,首先我们就需要把原问题分解成子问题,也就是子数组,使用分治技术可以将子数组划分为两个规模尽量相等的子数组,就是从数组中央分解。
第二步当分解到足够小,即只有一个元素的子数组,那么直接返回其值。
第三步合并:关于数组A的任何最大连续子数组A[i..j]所处的位置必然是以下三种情况:
1.完全位于数组左边A[low..mid]中,因此low<=j<=j<=mid,
2.完全位于子数组右边A[mid+1..high]中,因此mid<i<=j<=high,
3.跨越了中点,因此low<=i<=j<=high,
我们可以递归地求解A[low..mid]和A[mid+1..high]的最大子数组,因为这两个子问题依旧是最大子数组问题,只是规模更小.。剩下的是寻找跨越中点的最大子数组,然后在三种情况中选择最大的。此问题并非是比原问题更小规模的实例,因为它加入了限制---求出的子数组必须跨越中点。可以在线性时间内完成。我们只需找出形如A[low..mid]和A[mid+1..high]的最大子数组,然后将其合并即可。
int findMaxCrossingSubarray(int A[],int low,int mid,int high)
{
int leftSum = -999999;
int sum = 0;
for(int i = mid;i>=low;i--)
{
sum += A[i];
if(sum > leftSum)
{
leftSum = sum;
}
}
int rightSum = -99999;
sum = 0;
for(int i= mid;i<=high; i++)
{
sum += A[i];
if(sum > rightSum){
rightSum = sum;
}
}
return leftSum + rightSum;
}
递归代码如下:
int findMaximumSubarray(int A[],int low,int high)
{
if(high == low)
return A[low];
else {
int mid = (low + high)/2;
int maxSum = -999999;
int leftSum = findMaximumSubarray(A,low,mid);
if(leftSum > maxSum)
maxSum = leftSum;
int rightSum = findMaximumSubarray(A,mid+1,high);
if(rightSum > maxSum)
maxSum = rightSum;
int crossSum = findMaxCrossingSubarray(A,low,mid,high);
if(crossSum > maxSum)
maxSum = crossSum;
return maxSum;
}
}
其整体时间复杂度为nlogn;