最大子数组:一个数组A的和最大的非空连续子数组。
注意:只有当数组中包含负数时,最大子数组问题才有意义。
暴力求解法:(对每个子数组求和,选出和最大的子数组)
分治策略求解方法:
将数组分成规模尽可能相等的子数组,即找到中间位置,然后考虑左子数组和右子数组,最大子数组位置必然有以下三种情况:
- 完全位于左子数组中
- 跨过了中点
- 完全位于右子数组中
分别求出三种情况下的最大子数组,再选出三者中最大的子数组即为所求最大子数组。求最大左子树组和最大右子树组同样也是最大子数组问题,只是规模减半,因此可以递归求解。只需另外寻找跨过中点的最大子数组即可。
可以在线性时间内求出跨越中点的最大子数组:
def find_maxcrossing_subarray(A, low, mid, high):
'''将跨越中点的子数组依mid分成左、右两个部分,分别向左、右两边扩张求解最大值再相加,即为跨中点最大子数组。
注意:跨中点,即mid两边至少各有一个元素,否则不跨中点!'''
INF=9999999
left_SUM=-INF #左子数组和初始化为负无穷大,等会循环至少能加进来一个元素
SUM=0
min_left=mid #需要提前声明,弱无此条语句,无法返回循环内的局部变量值
for i in range(mid,low,-1):
SUM=SUM+A[i] #向左扩展一个元素
if SUM>left_SUM: #如果扩展后的和比当前左子数组和大,则更新,否则保持
left_SUM=SUM
min_left=i #标记左下标
#右子数组最大值求解和左子数组完全对称
right_SUM=-INF
SUM=0
max_right=mid+1
for j in range(mid+1,high):
SUM=SUM+A[j]
if SUM>right_SUM:
right_SUM=SUM
max_right=j
return(min_left,max_right,left_SUM+right_SUM)
然后就可以编写分治策略的代码:
def 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,cross_sum)=find_maxcrossing_subarray(A,low,mid,high)
#比较大小,返回三者中和最大的子数组
if left_sum>=right_sum and left_sum>=cross_sum:
return (left_low,left_high,left_sum)
elif right_sum>=left_sum and right_sum>=cross_sum:
return (right_low,right_high,right_sum)
else:
return (cross_low,cross_high,cross_sum)
作为算法导论中第一个实例问题,最大子数组问题理解起来非常容易。这也和符合分治策略的特点,用起来简单,但是分析复杂度比较困难,原因在于分治的特点是递归。所以学习的重点不应该局限于运用分治算法,还应掌握其算法分析。
书上的样例测试:
数组需要定义为:
A=[0,13,-3,-25,20,-3,-16,-23,18,20,-7,12,-5,-22,15,-4,7]
low=0,high=len(A)-1
直接print可得:
注:此系列为作者学习算法导论是记录的笔记,详情请阅读原书第四章,分治策略。