我们来思考如何用分治技术来求解最大子数组问题。假定我们要寻找子数组A[ lorw. . high]的最大子数组。使用分治技术意味着我们要将子数组划分为两个规模尽量相等的子数组。也就是说,找到子数组的中央位置,比如mid,然后考虑求解两个子数组A[low. . mid]和A[mid+1.. high]。如图4-4(a)所示,A[low. . high]的任何连续子数组A[i. . ij]所处的位置必然是以下三种情况之一-:
●完全位于子数组A[low. . mid]中,因此low≤i≤j≤mid.
●完全位于子数组A[mid+ 1.. high]中, 因此mid<i≤j≤high。
●跨越了中点,因此low≤i≤mid<j≤high。
因此,A[low. . high]的一个最大子数组所处的位置必然是这三种情况之一。实际上,A[lorw. . high]的一一个最大子数组必然是完全位于A[low. . mid]中、完全位于A[mid+ 1. . high]中或者跨越中点的所有子数组中和最大者。我们可以递归地求解A[low..mid]和A[mid+1.. high]的最大子数组,因为这两个子问题仍是最大子数组问题,只是规模更小。因此,剩下的全部工作就是寻找跨越中点的最大子数组,然后在三种情况中选取和最大者。
我们可以很容易地在线性时间(相对于子数组A[low..high]的规模)内求出跨越中点的最大子数组。此问题并非原问题规模更小的实例,因为它加入了限制求出的子数组必须跨越中点。如图4-4(b)所示,任何跨越中点的子数组都由两个子数组A[i. . mid]和A[mid+1.. j]组成,其中low≤i≤mid且mid<j≤high。因此,我们只需找出形如A[i.. mid]和A[mid+ 1.. ij]的最大子数组,然后将其合并即可。过程FIND-MAX- CORSSING-SUBARRAY接收数组A和下标low、mid和high为输人,返回-一个下标元组划定跨越中点的最大子数组的边界,并返回最大子数组中值的和。
FIND-MAX-CROSSING-SUBARRAY(A,low,mid,high)
left-sum = -∞
sum = 0
for i = mid downto low
sum = sum + A[i]
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)
此过程的工作方式如下所述。第1~7行求出左半部A[low. . mid]的最大子数组。由于此子数组必须包含A[mid],第3~7行的for循环的循环变量i是从mid开始,递减直至达到low,因此,它所考察的每个子数组都具有A[i.. mid]的形式。第1~2行初始化变量left-sum和sum,前者保存目前为止找到的最大和,后者保存A[i. mid]中所有值的和。每当第5行找到一个子数组A[i.. mid]的和大于left-sum 时,我们在第6行将left-sum更新为这个子数组的和,并在第7行更新变量max-left来记录当前下标i。第8~14求右半部A[mid+ 1.. high]的最大子数组,过程与左半部类似。此处,第10~14行的for循环的循环变量j是从mid+1开始,递增直至达到high,因此,它所考察的每个子数组都具有A[mid+1..j]的形式。最后,第15行返回下标max-left和max right,划定跨越中点的最大子数组的边界,并返回子数组A[max-left. . maxright. ]的和left sum+ right -sum。
如果子数组A[lorw..high]包含n个元素(即n=high- low+1), 则调用FIND-MAX-CROSSING SUBARRAY(A, low, mid, high)花费日(n)时间。由于两个for 循环的每次迭代花费0(1)时间,我们只需统计一共执行了多少次迭代。第3~7行的for循环执行了mid- lorw+1次迭代,第10~14行的for 循环执行了high- mid 次迭代,因此总循环迭代次数为
(mid一low+1)+ (high一mid) = high一low+1 = n
有了一个线性时间的FIND-MAX-CROSSING SUBARRAY在手,我们就可以设计求解最大子数组问题的分治算法的伪代码了:
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, corss-sum) = FIND-MAX-CROSSING-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, corss-sum)
初始调用FIND MAXIMUM-SUBARRAY(A, 1, A. length)会求出A[1.. n]的最大子数组。
与FIND MAX- CROSSING SUBARRAY相似,递归过程FIND-MAXIMUM SUBARRAY返回一个下标元组,划定了最大子数组的边界,同时返回最大子数组中的值之和。第1行测试基本情况,即子数组只有一个元素的情况。在此情况下,子数组只有-个子数组-它自身,因此第2行返回一个下标元组,开始和结束下标均指向唯- - 的元素,并返回此元素的值作为最大和。第3~11行处理递归情况。第3行划分子数组,计算中点下标mid。我们称子数组A[low. . mid]为左子数组,A[mid+1. . high]为右子数组。因为我们知道子数组A[low. . high]至少包含两个元素,则左、右两个子数组各至少包含--个元素。第4行和第5行分别递归地求解左右子数组中的最大子数组。第6~11行完成合并工作。第6行求跨越中点的最大子数组(回忆- -下,第6行求解的子问题并非原问题的规模更小的实例,因为我们将其看做合并部分)。第7行检测最大和子数组是否在左子数组中,若是,第8行返回此子数组。否则,第9行检测最大和子数组是否在右子数组中,若是,第10行返回此子数组。如果左、右子数组均不包含最大子数组,则最大子数组必然跨越中点,第11行将其返回。
伪代码实现:
C语言:
#include<stdio.h>
#include<limits.h>
struct result{
int maxLeft =0;
int maxRight = 0;
int maxSum = 0;
};//复习结构体的用法,所以用结构体来存放结果
struct result findMaxAcross(int A[],int low,int mid,int high){
int maxl=0;
int maxr =0;
struct result res;
int leftSum=-9999;//应该设置为无穷
int rightSum=-9999;//应该设置为无穷
int flag=0;
for(int i=mid;i>=low;i--){
flag+=A[i];
if(flag>leftSum){
leftSum=flag;
maxl=i;
}
}
res.maxLeft=maxl;
flag=0;
for(int j =mid+1;j<=high;j++){
flag+=A[j];
if(flag>rightSum){
rightSum=flag;
maxr=j;
}
}
res.maxRight=maxr;
res.maxSum=leftSum+rightSum;
return res;
}
struct result findMaxImum(int A[],int low,int high){
struct result res;
if(high==low){
res.maxLeft=low;
res.maxRight=high;
res.maxSum=A[low];
return res;
}else{
int mid=(low+high)/2;
struct result resLeft =findMaxImum(A,low,mid);
struct result resRight = findMaxImum(A,mid+1,high);
res = findMaxAcross(A,low,mid,high);
if(res.maxSum>=resLeft.maxSum&&res.maxSum>=resRight.maxSum){
return res;
}else if(resLeft.maxSum>=res.maxSum&&resLeft.maxSum>=resRight.maxSum){
return resLeft;
}else if(resRight.maxSum>=res.maxSum&&resRight.maxSum>=resLeft.maxSum){
return resRight;
}
}
return res;
}
int main(){
int n;
printf("请输入一个正整数\n");
scanf("%d",&n);
int A[n];
printf("请输入%d个整数:\n",n);
for(int i =0;i<n;i++){
scanf("%d",&A[i]);
}
struct result res =findMaxImum(A,0,n-1);
printf("最大子数组为:%d ~ %d\n",res.maxLeft,res.maxRight);
printf("最大子数组和为:%d\n",res.maxSum);
}
Java:
package org.example.分治策略.最大子数组问题;
import java.util.Scanner;
public class Main {
public static int[] findMaxAcross(int[] A, int low, int mid, int high){
int maxLeft = 0;
int maxRight = 0;
int[] result = new int[]{0,0,0};
int leftSum = (int) (-1.0/0);
int rightSum = (int) (-1.0/0);
int flag = 0;
for (int i=mid;i>=low;i--){
flag+=A[i];
if (flag>leftSum){
leftSum = flag;
maxLeft=i;
}
}
result[0]=maxLeft;
flag = 0;
for (int j= mid+1;j<=high;j++){
flag+=A[j];
if (flag>rightSum){
rightSum=flag;
maxRight=j;
}
}
result[1]=maxRight;
result[2]=leftSum+rightSum;
return result;
}
public static int[] findMaxImum(int[] A,int low,int high){
int[] result=new int[]{0,0,0};
int[] resultLeft=new int[]{0,0,0};
int[] resultRight = new int[]{0,0,0};
if (high==low){
result= new int[]{low, high, A[low]};
return result;
}else {
int mid=(low+high)/2;
resultLeft=findMaxImum(A,low,mid);
resultRight=findMaxImum(A,mid+1,high);
result=findMaxAcross(A,low,mid,high);
if (resultLeft[2]>=result[2]&&resultLeft[2]>=resultRight[2]){
return resultLeft;
} else if (resultRight[2]>=result[2]&&resultRight[2]>=resultLeft[2]) {
return resultRight;
} else if (result[2]>=resultLeft[2]&&result[2]>=resultRight[2]) {
return result;
}
}
return result;
}
public static void main(String [] args){
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int[] result = new int[3];
int[] A = new int[n];
for (int i =0;i<n;i++){
A[i]=scanner.nextInt();
}
result = findMaxImum(A,0,A.length-1);
System.out.println("最大子数组为:"+result[0]+"~"+result[1]);
System.out.println("最大子数组和为"+result[2]);
}
}
python:
def findMaxAcross(arr,low,mid,high):
maxLeft,maxRight,flag=0,0,0
result=[0,0,0]
leftSum,rightSum=-10000,-10000 #这里应该为负无穷
for i in range(mid,low-1,-1):
flag=flag+arr[i]
if flag>leftSum:
leftSum=flag
maxLeft=i
result[0]=maxLeft
flag=0
for j in range(mid+1,high+1,1):
flag=flag+arr[j]
if flag>rightSum:
rightSum=flag
maxRight=j
result[1] = maxRight
result[2]=leftSum+rightSum
return result
def findMaxImum(A,low,high):
if low==high:
result=[low,high,A[high]]
return result
else:
mid=int((low+high)/2)
resultLeft=findMaxImum(A,low,mid)
resultRight=findMaxImum(A,mid+1,high)
result=findMaxAcross(A,low,mid,high)
if result[2]>=resultRight[2] and result[2]>=resultLeft[2]:
return result
elif resultRight[2]>=result[2] and resultRight[2]>=resultLeft[2]:
return resultRight
elif resultLeft[2]>=result[2] and resultLeft[2]>resultRight[2]:
return resultLeft
return result
if __name__ == '__main__':
n =int(input("请输入一个正整数:"))
a=input("请输入 {} 个数字,以逗号隔开(例如:1,2,3):".format(n))
A=a.split(',')
A=[int(A[i]) for i in range(n)]
print(A)
result = findMaxImum(A,0,len(A)-1)
print("最大子数组为:{} ~ {}".format(result[0],result[1]))
print("最大子数组和为:{}".format(result[2]))