在算法导论4.1最大子数组问题中首先提出的是暴力求解方法即计算所有子数组的组合,然后求其和,寻找最大值。这种方法运行时间为Ω(n^2)。然后提出有没有更好的方法。
使用分治策略的求解方法:
寻找子数组A[low..high]的最大子数组,使用分治技术意味着要将子数组划分为两个规模尽量相等的子数组。也就是说,找到子数组的中央位置(比如mid),然后考虑求解两个子数组A[low..mid]和A[mid..high]。A[low..high]的任何连续子数组A[i..j]所处的位置必然以一下三种情况之一:
- 完全位于子数组A[low..mid]中,因此low≤i≤j≤mid.
- 完全位于子数组A[mid+1..high]中,因此mid<i≤j≤high.
- 跨越了中点,因此low≤i≤mid<j≤high.
因此,A[low..high]的一个最大子数组所处的位置必然是这三个情况之一即A[low..high]的一个最大子数组必然是完全位于A[low..mid]中、完全位于A[mid+1..high]中或者跨越中点的所有子数组和的最大者。递归地求解A[low..mid]和A[mid+1..high]的最大子数组,因为这两个仍是最大子数组问题,只是规模更小。因此,剩下全部工作就是寻找跨越中点的最大子数组,然后在三种情况中选取和最大者。
在线性时间内求出跨越中点的最大子数组。此问题并非原问题规模更小的实例因为加入了限制——求出的子数组必须跨越中点。任何跨越中点的子数组都由两个子数组A[i..mid]和A[mid+1..j]其中low≤i≤mid且mid<j≤high。因此找出形如A[i..mid]和A[mid+1..j]的最大子数组,然后合并即可。
FIND-MAX-CROOSSING-SUBARRAY(A, low, mid, high)伪代码:
1 //FIND-MAX-CROOSSING-SUBARRAY(A, low, mid, high) 2 left-sum = -∞ 3 sum = 0 4 for i = mid downto low 5 sum=sum+A[i] 6 if sum>left-sum 7 left-sum = sum 8 max-left = i; 9 right-sum = -∞ 10 sum = 0 11 for j = mid+1 to high 12 sum = sum +A[j] 13 if sum > right-sum 14 right-sum = sum 15 max-right = j 16 return(max-left, max-right, left-sum+right-sum)
FIND-MAXIMUM-SUBARRAY(A, low, high)伪代码:
//FIND-MAXIMUM-SUBARRAY(A, low, high) if high == low return(low, high, A[low]) //nase case: only one element else mid = (low+high)/2 (left-low, left-right, 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-MAX-CROSSARRAY(A, low, mid, high) if left-sum > right-sum and left-sum > cross-sum return (left-low, right-high, right-sum) else if right-sum > left-sum and right-sum > cross-sum return (right-low, right-high, right-sum) else if cross-sum > left-sum and cross-sum > right-sum return (cross-low, cross-high, cross-sum)
下面用Java语言实现最大子数组问题(分治策略实现)
FIND-MAX-CROOSSING-SUBARRAY(A, low, mid, high)方法实现
package dividerandconquer; public class FindMaxCrossingSubAraay { private int sum = 0; private int sumLeft = 0; private int sumRight = 0; private int maxLeft; private int maxRight; public void FindMaxCrossingSubAraay(int a[], int low, int mid, int high) { if (low == high) {//子数组个数为1 sum = a[low]; maxLeft = low; maxRight = low; }else {//子数组元素个数至少两个 sumLeft = a[mid]; sumRight = a[mid+1]; maxLeft = mid; maxRight = mid+1; for (int i = mid; i >= 0; i--) { sum+=a[i]; if (sum > sumLeft) { sumLeft = sum; maxLeft = i; } } sum = 0; for (int i = mid+1; i <= high; i++) { sum += a[i]; if (sum > sumRight) { sumRight = sum; maxRight = i; } } sum = sumLeft + sumRight; } } public int getSum() { return sum; } public int getSumLeft() { return sumLeft; } public int getSumRight() { return sumRight; } public int getMaxLeft() { return maxLeft; } public int getMaxRight() { return maxRight; } }
FIND-MAXIMUM-SUBARRAY(A, low, high)方法实现
package dividerandconquer; public class FindMaximumSubArray { private int low; private int high; private int sum; public void FindMaximumSubArray(int a[], int low, int high) { int mid; //中间位置 //左数组 int leftLow; int leftHigh; int leftSum; //右数组 int rightLow; int rightHigh; int rightSum; //中间数组 int crossLow; int crossHigh; int crossSum; if (high == low) {//只有一个元素 this.high = low; this.low = low; this.sum = a[low]; }else {//至少两个元素 mid =(int) ((low + high)/2);//向下取整 FindMaximumSubArray calLeft = new FindMaximumSubArray();//搜索左边 calLeft.FindMaximumSubArray(a, low, mid); leftLow = calLeft.getLow(); System.out.println("FindMaximumSubArray.leftLow="+leftLow); leftHigh = calLeft.getHigh(); System.out.println("FindMaximumSubArray.leftHigh="+leftHigh); leftSum = calLeft.getSum(); System.out.println("FindMaximumSubArray.leftSum="+leftSum); FindMaximumSubArray calRight = new FindMaximumSubArray();//搜索右边 calRight.FindMaximumSubArray(a, mid+1, high); rightLow = calRight.getLow(); System.out.println("FindMaximumSubArray.rightLow="+rightLow); rightHigh = calRight.getHigh(); System.out.println("FindMaximumSubArray.rightHigh="+rightHigh); rightSum = calRight.getSum(); System.out.println("FindMaximumSubArray.rightSum="+rightSum); FindMaxCrossingSubAraay calCross = new FindMaxCrossingSubAraay();//搜索从中间开始 calCross.FindMaxCrossingSubAraay(a, low, mid, high); crossLow = calCross.getMaxLeft(); System.out.println("FindMaxCrossingSubArray.crossLow="+crossLow); crossHigh = calCross.getMaxRight(); System.out.println("FindMaxCrossingSubArray.crossHigh="+crossHigh); crossSum = calCross.getSum(); System.out.println("FindMaxCrossingSubArray.crossSum="+crossSum); if ((leftSum >= rightSum) && (leftSum > crossSum)) { this.low = leftLow; this.high = leftHigh; this.sum = leftSum; }else if ((rightSum >= leftSum)&&(rightSum >= crossSum)) { this.low = rightLow; this.high = rightHigh; this.sum = rightSum; }else { this.low = crossLow; this.high = crossHigh; this.sum = crossSum; } } } public int getLow() { return low; } public int getHigh() { return high; } public int getSum() { return sum; } }
FIND-MAXIMUM-SUBARRAY(A, low, high)测试程序:
package dividerandconquer; import java.util.Random; public class testFindMaximumSubArray { int length;//getter & setter int a[] = new int[length];//setter int low; int high; int mid; int sum; Random random =new Random(); private void initTest1(){ low = 0; high =length-1; sum = 0; } private void cal(){ FindMaximumSubArray cal = new FindMaximumSubArray(); cal.FindMaximumSubArray(a, low, high); sum = cal.getSum(); low = cal.getLow(); high = cal.getHigh(); } private void calPrint() { // 输出数组 System.out.print("a=["); for (int i = 0; i < a.length; i++) { System.out.print(a[i]+" "); } System.out.println("]"); //输出最大子数组 System.out.print("a["+(low+1)+"..."+(high+1)+"]=["); for (int i = low; i <= high; i++) { System.out.print(a[i]+" "); } System.out.println("]"); System.out.println("sum="+sum); } public void test(){ initTest1(); cal(); calPrint(); } public int getLength() { return length; } public void setLength(int length) { this.length = length; } public void setA(int[] a) { this.a = a; } }
主入口程序:
package dividerandconquer; import java.util.Random; public class testFindMaximumSubArray { int length;//getter & setter int a[] = new int[length];//setter int low; int high; int mid; int sum; Random random =new Random(); private void initTest1(){ low = 0; high =length-1; sum = 0; } private void cal(){ FindMaximumSubArray cal = new FindMaximumSubArray(); cal.FindMaximumSubArray(a, low, high); sum = cal.getSum(); low = cal.getLow(); high = cal.getHigh(); } private void calPrint() { // 输出数组 System.out.print("a=["); for (int i = 0; i < a.length; i++) { System.out.print(a[i]+" "); } System.out.println("]"); //输出最大子数组 System.out.print("a["+(low+1)+"..."+(high+1)+"]=["); for (int i = low; i <= high; i++) { System.out.print(a[i]+" "); } System.out.println("]"); System.out.println("sum="+sum); } public void test(){ initTest1(); cal(); calPrint(); } public int getLength() { return length; } public void setLength(int length) { this.length = length; } public void setA(int[] a) { this.a = a; } }
运行结果:
a=[1 -2 3 10 -4 7 2 -5 ] a[3...7]=[3 10 -4 7 2 ] sum=18