分治算法-最大子数组问题

通用算法 专栏收录该内容
4 篇文章 0 订阅

背景不做过多介绍,现在有这么一个数组,里面都是整数型(包含负数),求最大子数组(连续几个相加最大)。

首先我们分析问题,我们把此数组看作A[low..high],我们将要用分治法求出其最大的子数组。使用分治法意味着我们要将数组划分为两个规模尽量相等的子数组(这里用尽量因为有时候是奇数个,无法均分),找到数组的中央位置,比如mid,然后考虑求解两个子数组A[low..mid]和A[mid+1..high]。那么子数组A[i..j]所有的情况都逃脱不了一下三种:

  1. 完全位于子数组A[low..mid]中,low<=i<=j<=mid
  2. 完全位于子数组A[mid+1..high]中,mid<i<=j<=high
  3. 跨越了中点,因此low<=i<=mid<j<=high
    那么我们可以递归的求解A[low..mid]和A[mid+1..high]的最大子数组,因为这两个子问题仍是最大数组问题,只是规模更小。因此剩下的工作就是寻找跨越中点的最大子数组,然后在三者中选取最大者。废话不多说,先上求跨越中点的伪代码:

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)
然后是主的递归伪代码:

FIND-MAXIMUM-SUBARRAY(A,low,high)
		if high==low
			return (low,high,A[low])
	    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-MAXIMUM-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)
最后是我的java实现:

package com.test;

import java.util.ArrayList;
import java.util.List;

public class MyTest02 {
	public static void main(String[] args) {
		int[] a = { 13, -3, -25, 20, -3, -16, -23, 18, 20, -7, 12, -5, -22, 15, -4, 7 };
		int[] s = getMaxSummary(a, 0, 15);
		for (int i = 0; i < s.length; i++) {
			System.out.println(s[i]);
		}
	}

	/**
	 * 程序主入口
	 * @param A
	 * @param low
	 * @param high
	 * @return
	 */
	public static int[] getMaxSummary(int[] A, int low, int high) {
		if (low == high) { // 如果長度就一個,那麼就把這個取出來
			int[] result = { low, high, A[low] };
			return result;
		} else {
			int middle = (int) Math.floor((low + high) / 2); // 获取中间值
			int[] left = new int[3]; // 保存左边部分返回结果
			int[] right = new int[3]; // 保存右边部分返回结果
			int[] cross = new int[3]; // 返回交叉部分返回结果
			left = getMaxSummary(A, low, middle);
			right = getMaxSummary(A, middle + 1, high);
			cross = getMaxCrossMid(A, low, high, middle);
			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;
			}
		}
	}

	/**
	 * 獲取最大子數組(一部分在左邊,一部分在右邊)
	 * 
	 * @param A
	 * @param low
	 * @param high
	 * @param middle
	 * @return
	 */
	public static int[] getMaxCrossMid(int[] A, int low, int high, int middle) {
		int leftSum = Integer.MIN_VALUE;
		int sum = 0; // 保存和的
		int left = 0; // 记录左边位置
		for (int i = middle; i >= low; i--) {
			sum = sum + A[i];
			if (sum > leftSum) { // 证明所加数字为正数,那么符合条件(因为最大子数组内正数越多指定越大)
				leftSum = sum;
				left = i;
			}
		}

		int rightSum = Integer.MIN_VALUE;
		int sum2 = 0;
		int right = 0; // 记录右边位置
		for (int i = middle + 1; i <= high; i++) {
			sum2 = sum2 + A[i];
			if (sum2 > rightSum) {
				rightSum = sum2;
				right = i;
			}
		}

		int[] result = new int[3];
		result[0] = left;
		result[1] = right;
		result[2] = leftSum + rightSum;
		return result;
	}

	
	
	
}




  • 2
    点赞
  • 7
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值