個人blog遷移到www.forwell.me
(在開始之前,請點擊下載源碼,後面的描述都是基於源碼進行的)
最大子數問題(本文不對分治法求最大子數的思想進行描述,重在實現,如果你對該算法不熟悉課閱讀《算法導論》4.1,中文版38-42頁,英文版67-73頁,或點擊瞭解)
《算法導論》中引入這個問題是通過股票的購買與出售,經過問題轉換,將前一天的當天的股票差價重新表示出來,即轉爲了一個最大子數組的問題,所謂最大子數問題就是在數列的一維方向找到一個連續的子數列,使該子數列的和最大。例如,對一個數列 −2, 1, −3, 4, −1, 2, 1, −5, 4,其連續子數列中和最大的是 4, −1, 2, 1, 其和爲6。我看到這個問題的想法就是遍歷所有的子串,算一下就能得出結果,沒錯這就是人類直接的土辦法,這也是《算法導論》4.1習題要求做的。下面就先寫暴力計算最大子數的僞代碼《算法導論》課後習題4.1-2:
FORCE_FIND_MAXMUM_SUBARRAY(A)
1 sum=0//記錄最大子串的值
2 n=A.length
3 for i=0 to n
4 temp_sum=0
5 for j=i to n
6 temp_sum = temp_sum+A[j]
7 if temp_sum>sum
8 low=i//記錄最大子串的開始下標
9 hight=j//記錄最大子串的結束下標
10 sum = temp_sum
11 return (low,hight,sum)
Java代碼實現
static ReturnResult ForceFindMaximumSubarray(int array[])
{
ReturnResult returnResult = new ReturnResult();
int arrayLength = array.length;
int tempSum=0;//記錄最大子串相加的中間變量
for(int i=0;i<arrayLength;i++)//從第一個元素開始遍歷所有子串
{
tempSum = 0;//新的開始需要初始化
for(int j=i;j<arrayLength;j++)
{
tempSum = tempSum+array[j];
if(tempSum>returnResult.getSum())//證明出現了比目前記錄最大的子串,則需要更行記錄
{
returnResult.setMaxLeft(i);
returnResult.setMaxRight(j);
returnResult.setSum(tempSum);
}
}
}
return returnResult;
}
公共類ReturnResult:
package com.tangbo;
public class ReturnResult {
int maxLeft;//最大子串開始下標
int maxRight;//最大子串結束下標
int sum;//子串的值
ReturnResult()
{
//初始化
maxLeft = 0;
maxRight = 0;
sum = 0;
}
public int getMaxLeft() {
return maxLeft;
}
public void setMaxLeft(int maxLeft) {
this.maxLeft = maxLeft;
}
public int getMaxRight() {
return maxRight;
}
public void setMaxRight(int maxRight) {
this.maxRight = maxRight;
}
public int getSum() {
return sum;
}
public void setSum(int sum) {
this.sum = sum;
}
@Override
public String toString() {
return "ReturnResult [maxLeft=" + maxLeft + ", maxRight=" + maxRight
+ ", sum=" + sum + "]";
}
}
很顯然這個算法的效率是O(n^2),但是分治法是O(nlgn),下面我們來看一下分治算法:分治算法就是將一個問題分解成若干個小問題,對若干個小問題分別求解,最後在合併起來,最大子段問題課以分成三種情況:1.數組a[1-n](即數組前1至n個元素) 的最大子段和a[1-n/2]的最大子段相同;(2)數組a[1-n](即數組前1至n個元素) 的最大子段和a[n/2+1-n]的最大子段相同;(3)數組a[1-n]的最大子段和是由a[1-n/2]和a[n/2+1-n]結合而成即最大子數在跨越中點。我們會發現(1)(2)情況通過遞歸最後都變成(3)這種情況,下面我們就來情況(3)的解決思想,僞代碼如下:
FIND_MAX_CROSSING_SUBARRAY(A,low,mid,high)
1 left_sum = -∞
2 sum=0
3 for i=mid to low
4 sum = sum+A[i]
5 if sum>left_sum
6 left_sum = sum
7 max_left=i
8 right_sum=-∞
9 sum=0
10 for j=mid+1 to hight
11 sum = sum+A[i]
12 if sum>right_sum
13 right_sum = sum
14 max_left=j
15 return (max_left,max_right, left_sum+ right_sum)
Java代碼實現爲:
static ReturnResult FindMaximumCrossingSubarray(int array[],int beginIndex,int mid,int endIndex)
{
ReturnResult returnResult = new ReturnResult();
int tempSum = 0;
int leftSum = 0 ;
int rightSum = 0;
//計算下半段最大子串
for(int i=mid;i>beginIndex-1;i--)
{
tempSum = tempSum+array[i];
if(tempSum>leftSum)
{
returnResult.setMaxLeft(i);
leftSum = tempSum;
}
}
tempSum = 0;//計算下半段最大子串
for(int j=mid+1;j<=endIndex;j++)
{
tempSum = tempSum + array[j];
if(tempSum>rightSum)
{
returnResult.setMaxRight(j);
rightSum = tempSum;
}
}
returnResult.setSum(rightSum+leftSum);//計算子串總和
return returnResult;
}
剩下的就是如何進行分治,我們是把數組分成一半,分別求左邊的最大子串,右面的最大子串和包含中點的最大子串,左邊和右邊的最大子串又可以轉化爲上述的三種情況,最後轉化爲第三種情況來實現,Java代碼如下:
//採用分治的方法來計算最大子串
static ReturnResult DivideAndConquerFindMaximumSubarray(int array[],int beginIndex,int endIndex)
{
int mid;
ReturnResult returnResult = new ReturnResult();
if(beginIndex==endIndex)
{
returnResult.setMaxLeft(beginIndex);
returnResult.setMaxRight(endIndex);
returnResult.setSum(array[beginIndex]);
return returnResult;
}else
{
mid = (beginIndex+endIndex)/2;
ReturnResult leftReturnResult,midReturnResult,rightReturnResult;
leftReturnResult = DivideAndConquerFindMaximumSubarray(array,beginIndex,mid);//求解完全位於左邊的最大子數
rightReturnResult = DivideAndConquerFindMaximumSubarray(array,mid+1,endIndex);//求解完全位於右邊的最大子數
midReturnResult = FindMaximumCrossingSubarray(array, beginIndex, mid, endIndex);//求解跨越中間節點的最大子數
//比較哪一個子串更大
if(leftReturnResult.getSum()>rightReturnResult.getSum() && leftReturnResult.getSum()>midReturnResult.getSum())
{
returnResult = leftReturnResult;
}else if(rightReturnResult.getSum()>leftReturnResult.getSum() && rightReturnResult.getSum()> midReturnResult.getSum())
{
returnResult = rightReturnResult;
}else
{
returnResult = midReturnResult;
}
}
return returnResult;
}
以上就是我自己對分治法求最大子串問題的理解,歡迎大家指正[email protected]。