归并排序中我们利用了分治策略,在分治策略中,我们递归求解一个问题,在每层递归中应用如下三个步骤:
分解
:将问题划分为一些子问题,子问题的形式与原问题一样,只是规模更小。解决
:递归的求解出子问题。如果子问题的规模足够小,则停止递归,直接求解。合并
:将子问题的解组合成原问题的解。当子问题足够大,需要递归求解时,称之为
递归情况
。当子问题变得足够小,不需要再递归时,递归已经"触底",进入了基本情况
。有时,除了与原问题形式完全一样的规模更小的子问题外,还需要求解与原问题不完全一样的子问题。这些子问题的求解看做合并步骤的一部分。
此次我们会通过最大子数组
和数组最大元素
两个案例来深入理解分治策略
前菜:数组最大元素问题
求解数组中最大元素的方法有很多,从
分治策略
的思路角度去解决问题,会有不一样的快感?。
- 分解:将数组以 mid 划分为左右两个子数组,递归分解
- 解决:当数组不可分时,处理
基本情况
- 合并:比较子数组返回的结果,将最大值返回。
示例代码
private static int maxValue(int[] array,int start,int end){
if (start==end){
//数组不可分时,返回即为最大值
return array[start];
}
//计算中点
int mid = (start+end)/2;
//递归求解左右子数组最大值
int l = maxValue(array,start,mid);
int r = maxValue(array,mid+1,end);
//返回处理结果
return l>r?l:r;
}
前菜上完,来点主菜加深理解吧?
主菜:最大子数组问题
最大子数组
指的是在包含负数的数组中,寻找和最大的连续子数组。(非负数整个数组就是最大的)
分析
假设寻找 A[low,high] 的最大子数组,分治策略意味着我们要将子数组划分为两个规模尽量相等的子数组。比如找到数组的中间位置 mid,然后考虑求解两个子数组 A[low,mid],A[mid+1,high]。而 A[low,high]的任何连续子数组A[i,j]所处的位置必然是以下三种情况之一:
左边最大
,即完全位于A[low,mid]中,low ≤ i ≤ j ≤ mid右边最大
,即完全位于A[mid+1,high],mid < i ≤ j ≤ high跨越中点
,即low ≤ i ≤ mid < j ≤ high
前两种为相同形式的子问题,而对于跨越中点
情况,我们需要特殊求解
跨越中点
跨越中点
为特殊情况,需要额外处理:以 mid 为起始点,向左计算累加和最大元素位置,向右计算累加最大和元素位置,将元素位置与左右累加和返回。示例代码如下:
/**
* 计算跨越 mid 情况下的最大数组和
*
* @param array 待计算数组
* @param start 起始位置
* @param mid 中点位置
* @param end 结束位置
* @return int[最大子数组起始位置、最大子数组结束位置、最大子数组数值]
*/
private static int[] maxCrossSum(int[] array, int start, int mid, int end) {
int sum = 0;
int leftSum = Integer.MIN_VALUE;
int rightSum = Integer.MIN_VALUE;
int leftPos = 0, rightPos = 0;
//计算左侧累加最大值和位置
for (int i = mid; i >= start; i--) {
System.out.println("array ::: " + array[i]);
sum = sum + array[i];
if (sum > leftSum) {
leftSum = sum;
leftPos = i;
}
}
System.out.println("start --- mid : max left sum is " + leftSum + "; max left position is " + leftPos);
sum = 0;
//计算右侧累加最大值和位置
for (int i = mid + 1; i <= end; i++) {
System.out.println("array ::: " + array[i]);
sum = sum + array[i];
if (sum > rightSum) {
rightSum = sum;
rightPos = i;
}
}
System.out.println("mid --- end : max right sum is " + rightSum + "; max right position is " + rightPos);
//返回最大和数组
return new int[]{
leftPos, rightPos, leftSum + rightSum};
}
整体求解
处理好特殊情况,我们就可以按照
分治策略
去解决问题了。扫描二维码关注公众号,回复: 12013546 查看本文章
- 分解: 以数组的中间位置划分两个子数组,递归分解问题。分别比较左边、右边、跨越中点的情况。
- 解决:当数组元素个数为1时,进入
基本情况
,求解问题。- 合并:将子问题的结果合并,判断最大值并返回结果。
示例代码:
/**
* 对于分析中的1、2两种情况,可以通过递归拆分为相同子问题,因为都是在求指定上限(start 和 end 是确定的)的最大和子数组问题。
* 第3种情况则特殊处理即可
*
* @param array 待计算数组
* @param start 数组起始位置
* @param end 数组结束位置
* @return int[最大子数组起始位置、最大子数组结束位置、最大子数组数值]
*/
private static int[] find(int[] array, int start, int end) {
//如果数组中只有一个元素,直接返回当前元素
if (end == start) {
System.out.println("end == start" + start + " array value = " + array[start]);
return new int[]{
start, end, array[start]};
}
int mid = (start + end) / 2;
//分别当前数组左侧最大值、右侧最大值、和包含 mid 最大值
int[] left = find(array, start, mid);
int[] right = find(array, mid + 1, end);
int[] cross = maxCrossSum(array, start, mid, end);
//比较最大值并返回
if (left[2] > right[2] && left[2] > cross[2]) {
return left;
} else if (right[2] > left[2] && right[2] > cross[2]) {
return right;
} else {
return cross;
}
}
出差回来就开始写,终于写到了这里,哈哈哈,继续努力!下一篇《排序算法之堆排序》