最大子数组问题:《算法导论4.1-3》(CLRS Solutions)

最大子数组:

在一个数组A中寻找和最大的非空连续子数组。我们称这样的连续子数组为最大子数组

注:1、我们通常说“一个最大子数组”而不是“最大子数组”,因为可能有多个子数组达到最大和。
​       2、只有当数组中包含负数时,最大子数组问题才有意义。如果所有元素都是非负的,整个数组的和肯定是最大的。

使用分治策略的求解方法

将数组A划分为两个规模尽量相等的子数组,找到中央位置 mid,所以数组A中任何连续子数组A [i … j ]所处位置必然是以下情况:

  • 位于前半部分A[low…mid]中,因此 low ≤ i ≤ j ≤ mid
  • 位于后半部分A[mid+1…high]中,因此 mid < i ≤ j ≤ high
  • 跨越了中点,因此 low ≤ i ≤ mid < j ≤ high

我们可以递归的求解 A[low…mid]A[mid+1…high] 的最大子数组,因为这两个子问题仍然是最大子数组问题,符合递归条件。剩下的全部工作就是寻找跨中点的子数组,然后在三种情况中取和最大者。

在解决跨中点的子数组时,其中 low ≤ i ≤ midmid < j ≤ high 因此,我们只需找出中点前后两部分的最大子数组,然后将其合并即可。



以下面数组为例,分别以递归算法和暴力算法来实现:





伪代码如下:

FIND-MAX-CROSSING-SUBARRAY(A,low,mid,high)
1  left-sum = -//初始化变量
2  sum = 0
3  for i = mid downto low//左半部分
4      sum = sum + A[i]
5      if sum > left-sum
6          left-sum = sum     	//更新left-sum为这个子数组的和
7          max-left = i   		//更新变量max-left来记录当前下标i
8  right-sum = -//右半部分
9  sum = 0
10 for j = mid + 1 to high
11     sum = sum + A[j]
12     if sum > right-sum
13         right-sum = sum
14         max-right = j
15 return(max-left,max-right,left-sum + right-sum)    //返回左右边界i和j与最大子数组总和

FIND-MAXIMUM-SUBARRAY(A,low,high)
1 if high == low                        //检测子数组只有一个元素的情况
2     return(low,high,A[low])
 
3 else mid =(low + high) / 2//划分子数组
/**分别求解左右部分最大子数组**/
4     (left-low,left-high,left-sum) = 
            FIND-MAXIMUM-SUBARRAY(A,low,mid)
5     (right-low,right-high,right-sum) = 
            FIND-MAXIMUM-SUBARRAY(A,mid+1,high)
/**完成合并**/
6     (cross-low,cross-high,cross-sum) = 
            FIND-MAX-CROSSING-SUBARRAY(A,low,mid,high)//跨越中点最大子数组
    /**三者互相比较,找出和最大的(子数组)**/
7     if left-sum >= right-sum and left-sum >= cross-sum
8           return(left-low,left-high,left-sum)
9     elseif right-sum >= left-sum and right-sum >= cross-sum
10          return(right-low,right-high,right-sum)
11    else return(cross-low,cross-high,cross-sum)



递归算法实现:

public class Subarray {
	//如果最大子数组横跨左右部分数组
	public static int[] FindMaxCrossingSubarray(int[] A, int low, int mid, int high) {
		int[] result = new int[3];
		//从中间点向左遍历,找出左半部分过中点的最大子数组
		int sum = 0;
		int i;
		for (i=mid;i>=low;i--) {
			sum = sum + A[i];
			if (sum>result[2]) {
				result[2] = sum;
				result[0] = i;
			}
		}
		//从中间往右遍历,找出右半部分过中点的最大子数组
		sum = 0;
		result[2] = 0;
		for (i=mid;i<=high;i++) {
			sum = sum + A[i];
			if (sum>result[2]) {
				result[2] = sum;
				result[1] = i;
			}
		}
		//将左右两个子数组组合
		result[2] = 0;
		for (i = result[0];i<=result[1];i++) {
			result[2] = result[2] + A[i];
		}
		return result;
	}
    
	//将数组分成左右两个部分,依次计算左数组、右数组的最大子数组,再计算横跨左右部分的中间子数组,选择最大的输出
	public static int[] FindMaximumSubarray(int[] A, int low, int high) {
		int[] result = new int[3];
		int[] result_left = new int[3];
		int[] result_right = new int[3];
		int[] result_cross = new int[3];
		int mid = (int)(low+high)/2;
		//基本情况:数组中只有一个元素,则返回该数组
		if(low==high) {
			result[0]=low;
			result[1]=high;
			result[2]=A[low];
			return result;
		}
		//递归情况:数组中多于一个元素,依次计算三种情况下的最大子数组
		else {
			result_left = FindMaximumSubarray(A, low, mid);
			result_right = FindMaximumSubarray(A, mid+1, high);
			result_cross = FindMaxCrossingSubarray(A, low, mid, high);
			//比较三种情况的最大子数组,选择最大的输出
			if (result_left[2]>result_right[2] && result_left[2]>result_cross[2]) {
				return result_left;
			}
			else if (result_right[2]>result_left[2] && result_right[2]>result_cross[2]) {
				return result_right;
			}
			else {
				return result_cross;
			}
		}
	}
	public static void main(String[] args) {
		int[] result = new int[3];
		int[] A = {13, -3, -25, 20, -3, -16, -23, 18, 20, -7, 12, -5, -22, 15, -4, 7};
		result = FindMaximumSubarray(A, 0, 15);
		System.out.print("最大子数组为 : ");
		for(int i=result[0]; i<=result[1];i++) {
			System.out.print(A[i]+" ");
		}
		System.out.print("\n最大子数组和 : "+result[2]);
	}
}

运行结果如下:





暴力实现:

public class SubArray {

	public int bruteMethod(int[] A) {
		int maxResult = A[0];
		int maxTemp = 0;
		;
		for (int i = 0; i < A.length; i++) {
			for (int j = i; j < A.length; j++) {
				for (int k = i; k <= j; k++) {
					maxTemp += A[k];
				}
				if (maxTemp > maxResult)
					maxResult = maxTemp;
				maxTemp = 0; // 完成一个子序列求和后,重新赋值为0
			}
		}
		return maxResult;
	}

	public static void main(String[] args) {
		MaxSubArray test = new MaxSubArray();
		int[] A = { 13, -3, -25, 20, -3, -16, -23, 18, 20, -7, 12, -5, -22, 15, -4, 7 };
		System.out.println("暴力法求解数组A的最大子数组和为:" + test.bruteMethod(A));
	}
}

运行结果如下:



讨论两种算法的性能:

暴力算法的复杂度是O(n2),递归算法是O ( n lg ( n ) ),先计算出交叉点,再经过大规模测试数据,由于由于n0的存在,在 [0 , n0] 间是暴力法较优,在 [n0 , +∞] 是递归法较优。

  1. 若n0小于等于原交叉点X0时,交叉点不改变,因为在原交叉点[n0 , x0]仍是递归,且此时递归处理的效率不如暴力算法,从 x0 之后递归的效率才超出。
  2. 若n0大于原交叉点x0时,交叉点更新为n0 ,此时改良后的算法在 [0 , x0] 时暴力较优,在 [x0 , n0] 之间,虽然递归效率更高,但是由于仍然使用暴力法,所以并没有性能交叉,而在 [n0 , +∞] 之后,采用了递归法,且由于x0 < n0 ,此时递归效率高于暴力算法,所以性能交叉点变为 n0
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值