1. 程式人生 > >分治演算法-最大子陣列問題

分治演算法-最大子陣列問題

背景不做過多介紹,現在有這麼一個數組,裡面都是整數型(包含負數),求最大子陣列(連續幾個相加最大)。

首先我們分析問題,我們把此陣列看作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;
	}

	
	
	
}