《算法導論》學習心得(一)——分治求最大子數問題

個人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]

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章