Algorithm —— 最大子陣列求解(五)
阿新 • • 發佈:2019-02-06
Algorithm —— 最大子陣列求解
《演算法導論》中引出了一個求某個陣列的和最大子陣列問題:在原陣列A中,求一個子陣列a,它的各元素值的和是A的各個子陣列中最大的;且子陣列a的各元素下標值要連續。
要注意的是,如果要求某個陣列的最大子陣列,則此陣列中的值必須要包含負值;否則,求最大子陣列是沒有意義的,因為此時,整個陣列的和肯定是最大的,就不必再求了。
我們使用分治策略來解決該問題。假定我們要尋找陣列A[low...high]的最大子陣列,因為要使用分治技術,這也就意味著我們要將陣列A劃分為兩個規模儘量相等的子陣列。也就是找到陣列A的中央位置mid,然後考慮求解兩個子陣列A[low...mid]和A[mid+1...high]。
我們可以知道,陣列A[low...high]的任何連續子陣列A[i...j]所處的位置必然是一下三種情況之一:
- 完全位於子陣列A[low...mid]中,因此low =< i =< j =< mid;子陣列完全位於A[low...mid]中。
- 完全位於子陣列A[mid+1...high]中,因此mid < i =<j =< high;子陣列完全位於A[mid+1...high]中。
- 跨越了中點mid,因此lwo =< i =< mid < j =< high;子陣列跨越了中點mid。
因此,A[low...high]的一個最大子陣列所處的位置必然是上述三種情況之一。對於前兩種情況,我們可以遞迴求解,因為 這兩個問題仍是最大子陣列問題,只是規模更小;剩下的工作就是尋找跨越中點的最大子陣列。所以問題的最終結果就是在三種情況中選取和最大者。
尋找跨越中點的最大子陣列問題並不是原問題規模更小的例項,因為它加入了限制——求出的子陣列必須跨越中點。任何跨越中點的子陣列都由兩個子陣列A[i...mid]和A[mid+1...j]組成,其中low =< i =< mid < j =< high。因此,我們只需找出形如A[i...mid]和A[mid+1...j]的最大子陣列,然後將其合併即可。此問題的函式如下所示:
此時,我們就解出了求跨越中點的最大子陣列問題;又由於前兩種情況是原問題規模更小的例項,可用遞迴求解;所以此時求陣列A[low...high]的最大子陣列問題的解如下:/** * * @author coder * * 代表'和最大子陣列'資訊的封裝型別 * */ private class MaxSumSubArray { public MaxSumSubArray(int left_index, int right_index, int sum) { this.left_index = left_index; this.right_index = right_index; this.sum = sum; } int left_index;// 陣列的左下標邊界值 int right_index;// 陣列的右下標邊界值 int sum;// 此子陣列各元素的和值 @Override public String toString() { return "MaxSumSubArray [left_index=" + left_index + ", right_index=" + right_index + ", sum=" + sum + "]"; } } /** * 返回一個跨越中點的'和最大子陣列'的邊界,及此陣列中所有值的和;因為要尋找的'和最大子陣列'下標必須要跨越中點,所以該子陣列的下標範圍一定是從中點向左右兩邊延伸、且連續; * * 對於左陣列,我們只需在下標範圍[low...mid]中,從mid值開始由右向左依次計算各元素相加的和,並逐一比較各次所得和的大小;記錄當前得到的最大的和與此時運算的陣列下標值i;迴圈結束後,就得到了這個和最大子陣列的左半部分a[i...mid] * 對於右陣列,也是類似,從(mid+1)開始,從左向右遍歷計算各元素相加的和;得到當前最大的和值時的下標j;迴圈結束後,就得到這個子陣列的右半部分b[mid+1...high] * * 合併子陣列a/b,就得到了當前跨越中點的'和最大子陣列'c[i...j]; * * @param array * 當前需要處理的陣列 * @param low * 在當前陣列中需要處理的子陣列的下標左邊界 * @param mid * 在當前數子組中需要處理的下標範圍對應的下標中點 * @param heigh * 在當前陣列中需要處理的子陣列的下標右邊界 * @return 返回一個MaxSumSubArray例項,即 跨越中點的'和最大子陣列'的封裝物件 */ private MaxSumSubArray findMaxCrossingSubArray(int[] array, int low, int mid, int high) { int left_sum = 0;// 記錄每次左邊找到的最大和值 int sum = 0; // 記錄左邊子陣列所有元素的和值 int max_left = -1;// 記錄每次找到左邊子陣列最大和值時的下標值 for (int i = mid; i >= low; i--) { sum = sum + array[i]; if (sum > left_sum) { left_sum = sum; max_left = i; } } int right_sum = 0;// 記錄每次右邊找到的最大和值 sum = 0;//// 記錄右邊子陣列所有元素的和值 int max_right = -1;// 記錄每次找到右邊子陣列最大和值時的下標值 for (int j = mid + 1; j <= high; j++) { sum = sum + array[j]; if (sum > right_sum) { right_sum = sum; max_right = j; } } MaxSumSubArray obj = new MaxSumSubArray(max_left, max_right, left_sum + right_sum); return obj; }
/**
*
* @author coder
*
* 代表'和最大子陣列'資訊的封裝型別
*
*/
private class MaxSumSubArray {
public MaxSumSubArray(int left_index, int right_index, int sum) {
this.left_index = left_index;
this.right_index = right_index;
this.sum = sum;
}
int left_index;// 陣列的左下標邊界值
int right_index;// 陣列的右下標邊界值
int sum;// 此子陣列各元素的和值
@Override
public String toString() {
return "MaxSumSubArray [left_index=" + left_index + ", right_index=" + right_index + ", sum=" + sum + "]";
}
}
/**
* 找到目標陣列array的一個'和最大子陣列',並返回該'和最大子陣列'的下標及所有元素和資訊
*
* @param array
* 目標源陣列
* @param low
* 需要操作的子陣列的下標左邊界
* @param high
* 需要操作的子陣列的下標右邊界
* @return 返回從目標值陣列中指定的子陣列c[low...high]的'和最大子陣列'資訊
*/
public MaxSumSubArray findMaxImumSubArray(int[] array, int low, int high) {
if (low > high || array == null) {
throw new IllegalArgumentException("findMaxImumSubArray Argument is illegal!");
}
if (high == low) {
return new MaxSumSubArray(low, high, array[low]);
}
int mid = (low + high) / 2;
MaxSumSubArray leftMaxSSArray = findMaxImumSubArray(array, low, mid);
MaxSumSubArray rightMaxSSArray = findMaxImumSubArray(array, mid + 1, high);
MaxSumSubArray crossMidMaxSSArray = findMaxCrossingSubArray(array, low, mid, high);
if (leftMaxSSArray.sum >= rightMaxSSArray.sum && leftMaxSSArray.sum >= crossMidMaxSSArray.sum)
return leftMaxSSArray;
else if (rightMaxSSArray.sum >= leftMaxSSArray.sum && rightMaxSSArray.sum >= crossMidMaxSSArray.sum)
return rightMaxSSArray;
return crossMidMaxSSArray;
}
/**
* 返回一個跨越中點的'和最大子陣列'的邊界,及此陣列中所有值的和;因為要尋找的'和最大子陣列'下標必須要跨越中點,所以該子陣列的下標範圍一定是從中點向左右兩邊延伸、且連續;
*
* 對於左陣列,我們只需在下標範圍[low...mid]中,從mid值開始由右向左依次計算各元素相加的和,並逐一比較各次所得和的大小;記錄當前得到的最大的和與此時運算的陣列下標值i;迴圈結束後,就得到了這個和最大子陣列的左半部分a[i...mid]
* 對於右陣列,也是類似,從(mid+1)開始,從左向右遍歷計算各元素相加的和;得到當前最大的和值時的下標j;迴圈結束後,就得到這個子陣列的右半部分b[mid+1...high]
*
* 合併子陣列a/b,就得到了當前跨越中點的'和最大子陣列'c[i...j];
*
* @param array
* 當前需要處理的陣列
* @param low
* 在當前陣列中需要處理的子陣列的下標左邊界
* @param mid
* 在當前數子組中需要處理的下標範圍對應的下標中點
* @param heigh
* 在當前陣列中需要處理的子陣列的下標右邊界
* @return 返回一個MaxSumSubArray例項,即 跨越中點的'和最大子陣列'的封裝物件
*/
private MaxSumSubArray findMaxCrossingSubArray(int[] array, int low, int mid, int high) {
int left_sum = 0;// 記錄每次左邊找到的最大和值
int sum = 0; // 記錄左邊子陣列所有元素的和值
int max_left = -1;// 記錄每次找到左邊子陣列最大和值時的下標值
for (int i = mid; i >= low; i--) {
sum = sum + array[i];
if (sum > left_sum) {
left_sum = sum;
max_left = i;
}
}
int right_sum = 0;// 記錄每次右邊找到的最大和值
sum = 0;//// 記錄右邊子陣列所有元素的和值
int max_right = -1;// 記錄每次找到右邊子陣列最大和值時的下標值
for (int j = mid + 1; j <= high; j++) {
sum = sum + array[j];
if (sum > right_sum) {
right_sum = sum;
max_right = j;
}
}
MaxSumSubArray obj = new MaxSumSubArray(max_left, max_right, left_sum + right_sum);
return obj;
}
完整的測試程式碼如下:public class AlgorithmTest {
public static void main(String[] args) {
int[] array = { 13, -3, -25, 20, -3, -16, -23, 18, 20, -7, 12, -5, -22, 15, -4, 7 };
AlgorithmTest algorithm = new AlgorithmTest();
MaxSumSubArray res = algorithm.findMaxImumSubArray(array, 0, array.length - 1);
System.out.println(res);
}
/**
*
* @author coder
*
* 代表'和最大子陣列'資訊的封裝型別
*
*/
private class MaxSumSubArray {
public MaxSumSubArray(int left_index, int right_index, int sum) {
this.left_index = left_index;
this.right_index = right_index;
this.sum = sum;
}
int left_index;// 陣列的左下標邊界值
int right_index;// 陣列的右下標邊界值
int sum;// 此子陣列各元素的和值
@Override
public String toString() {
return "MaxSumSubArray [left_index=" + left_index + ", right_index=" + right_index + ", sum=" + sum + "]";
}
}
/**
* 找到目標陣列array的一個'和最大子陣列',並返回該'和最大子陣列'的下標及所有元素和資訊
*
* @param array
* 目標源陣列
* @param low
* 需要操作的子陣列的下標左邊界
* @param high
* 需要操作的子陣列的下標右邊界
* @return 返回從目標值陣列中指定的子陣列c[low...high]的'和最大子陣列'資訊
*/
public MaxSumSubArray findMaxImumSubArray(int[] array, int low, int high) {
if (low > high || array == null) {
throw new IllegalArgumentException("findMaxImumSubArray Argument is illegal!");
}
if (high == low) {
return new MaxSumSubArray(low, high, array[low]);
}
int mid = (low + high) / 2;
MaxSumSubArray leftMaxSSArray = findMaxImumSubArray(array, low, mid);
MaxSumSubArray rightMaxSSArray = findMaxImumSubArray(array, mid + 1, high);
MaxSumSubArray crossMidMaxSSArray = findMaxCrossingSubArray(array, low, mid, high);
if (leftMaxSSArray.sum >= rightMaxSSArray.sum && leftMaxSSArray.sum >= crossMidMaxSSArray.sum)
return leftMaxSSArray;
else if (rightMaxSSArray.sum >= leftMaxSSArray.sum && rightMaxSSArray.sum >= crossMidMaxSSArray.sum)
return rightMaxSSArray;
return crossMidMaxSSArray;
}
/**
* 返回一個跨越中點的'和最大子陣列'的邊界,及此陣列中所有值的和;因為要尋找的'和最大子陣列'下標必須要跨越中點,所以該子陣列的下標範圍一定是從中點向左右兩邊延伸、且連續;
*
* 對於左陣列,我們只需在下標範圍[low...mid]中,從mid值開始由右向左依次計算各元素相加的和,並逐一比較各次所得和的大小;記錄當前得到的最大的和與此時運算的陣列下標值i;迴圈結束後,就得到了這個和最大子陣列的左半部分a[i...mid]
* 對於右陣列,也是類似,從(mid+1)開始,從左向右遍歷計算各元素相加的和;得到當前最大的和值時的下標j;迴圈結束後,就得到這個子陣列的右半部分b[mid+1...high]
*
* 合併子陣列a/b,就得到了當前跨越中點的'和最大子陣列'c[i...j];
*
* @param array
* 當前需要處理的陣列
* @param low
* 在當前陣列中需要處理的子陣列的下標左邊界
* @param mid
* 在當前數子組中需要處理的下標範圍對應的下標中點
* @param heigh
* 在當前陣列中需要處理的子陣列的下標右邊界
* @return 返回一個MaxSumSubArray例項,即 跨越中點的'和最大子陣列'的封裝物件
*/
private MaxSumSubArray findMaxCrossingSubArray(int[] array, int low, int mid, int high) {
int left_sum = 0;// 記錄每次左邊找到的最大和值
int sum = 0; // 記錄左邊子陣列所有元素的和值
int max_left = -1;// 記錄每次找到左邊子陣列最大和值時的下標值
for (int i = mid; i >= low; i--) {
sum = sum + array[i];
if (sum > left_sum) {
left_sum = sum;
max_left = i;
}
}
int right_sum = 0;// 記錄每次右邊找到的最大和值
sum = 0;//// 記錄右邊子陣列所有元素的和值
int max_right = -1;// 記錄每次找到右邊子陣列最大和值時的下標值
for (int j = mid + 1; j <= high; j++) {
sum = sum + array[j];
if (sum > right_sum) {
right_sum = sum;
max_right = j;
}
}
MaxSumSubArray obj = new MaxSumSubArray(max_left, max_right, left_sum + right_sum);
return obj;
}
public void arrayPrint(int[] array) {
System.out.print("[");
for (int i = 0; i < array.length; i++) {
System.out.print(array[i] + ", ");
}
System.out.println("]");
}
}
輸出如下:MaxSumSubArray [left_index=7, right_index=10, sum=43]
求陣列的最大子陣列問題就解決了。