Python 分治法-最大子数组问题(我本身有面试遇到过)

学习算法导论的时候,有一个买股票的例子,我以为挺有意思的,书上是这么说的: 假定你得到了投资挥发性化学公司的机会。与其生产的化学制品同样,这家公司的股票价格也是不稳定的。你被准许能够在某个时刻买进一股该公司的股票,并在以后某个日期将其卖出,买进卖出都是在当天交易结束后进行。为了补偿这一限制,你能够了解股票未来的价格。你的目标是最大化收益。图4-1给出了17天内的股票价格。第0天的股票价格是每股100美圆,你能够在此以后任什么时候间买进股票。你固然但愿“低价买进.高价卖出”—在最低价格时买进股票,以后在最高价格时卖出,这样能够最大化收益。但遗憾的是,在一段给定时期内,可能没法作到在最低价格时买进股票,而后在最高价格时卖出.例如,在图4-1中,最低价格发生在第7天,而最高价格发生在第1天—最高价在前,鼓低价在后。其实就是求最大连续子数组的问题,求在连续的买入股票,使得收益最大,固然是用暴力求解的方法也能够求出来,时间复制度为O(n^2)。不过有更好的方法,那就是分治法,时间复杂度为O(nlgn)算法

暴力解决:

#暴力求解最大子数组
def find_max_list(listn,n):                #listn:要求解的数组 n:数组大小
    sum=0
    list_result=[listn[0],0,0]   #结果为列表,存储和的最大值和结果子数组的位置 
    for i in range(n-1):   #这里只循环到第listn[n-2],考虑到当i=n-1时j的取值
        sum=listn[i]           #i变更时,sum进入新的循环,从新赋值为listn[i]
        if sum>list_result[0]:
            list_result=[sum,i,i]
        for j in range(i+1,n,1):
            sum=sum+listn[j]
            if sum>list_result[0]:
                list_result=[sum,i,j]
    if list_result[0]<listn[-1]:                #单独与最后一个数字进行比较
        list_result=[listn[-1],n-1,n-1]
    return sum(list_result)
复制代码
分治法分三步:
复制代码
  1. 分:能够将数组分为两部分,要尽可能均匀,因此最好是从中间分,结果能够能在前半数组,可能在后半数组,也可能跨先后数组编程

  2. 治:咱们能够递归求解前半数组最大值,递归求解后半数组最大值,再求解跨先后数组最大值数组

  3. 合并:将求得的值返回,比较求解到的三个最大值,返回其中的最大值bash

在个人理解中,分治法就是将一个大问题以迭代的方式不断分解为小问题,直到最后分解的小问题的规模小到可以以很小的代价求解出来,再将这些小问题的解综合起来以获得开始的大问题的解。这里面两个难点,一是如何经过迭代将大问题分解成易于求解的小问题,即“分”的问题,二是如何将这些小问题的解综合或者说还原成大问题的解,即“治”。 “分”的话,很容易便想到将数组不断地对半分,直至最终分得的小问题规模只有一个元素,那么这个小问题的解也就是这个数字了,但问题在于“治”,这样的分法有一个问题,就是将n个数字分红1—n/2和n/2+1—n后(这里为了便于叙述,就不讨论n/2不是整数的状况了,在编写程序时能够用很简单的取整函数解决这个问题),若是只在这两个部分再次进行分解和计算的话,就会忽略解是贯穿两个部分的数组这样的可能性,因此必需要在分的时候计算通过n/2的最大子数组,求解这个子数组的方式就是分别从n/2和n/2+1往左和往右对其余元素进行相加和比较,直到找到两边的最大值,而后进行相加即获得。函数

#求解中间的最大子数组
def find_max_cross(l1,left,right):
    #初始化左右两边的和以及解的坐标
    leftmax=-float('inf')
    rightmax=-float('inf')
    ileft=-float('inf')
    iright=-float('inf')
    maxlist=[]
    sum=0
    mid=(left+right)//2
    #以mid为右端坐标往左进行寻找和的最大值
    for i in range(mid,left-1,-1):
        sum=sum+l1[i]
        if sum>leftmax:
            leftmax=sum
            ileft=i
    sum=0
    #以mid+1为左端往右进行寻找和的最大值
    for i in range(mid+1,right+1,1):
        sum=sum+l1[i]
        if sum>rightmax:
            rightmax=sum
            iright=i
    #将两侧的结果相加,并取得相应的坐标返回
    maxlist=[leftmax+rightmax,ileft,iright] 
    return maxlist
复制代码

在找到中间的最大子数组以后,剩余的可能为解的子数组就只能在1–n/2和n/2+1–n这两个区间里了,而在这两个区间里的解就能够经过不断的迭代来求解,而后在每次迭代时比较三者的大小从而取其最大值。学习

#分治法进行迭代
def find_all(l1,left,right):
    maxlist2=[]
    #最小规模:只有一个数字的子数组
    if left==right:
        maxlist2=[l1[left],left,right]
        return maxlist2
    else:
    #左右中三部分的最大子数组信息
        lml=[]
        rml=[]
        mml=[]
        m=(left+right)//2
        #主函数find_all的迭代,迭代的同时也开始在“治”
        lml=find_all(l1,left,m)
        rml=find_all(l1,m+1,right)
        mml=find_max_cross(l1,left,right)
        #进行比较取最大值
        if lml[0]>=rml[0] & lml[0]>=mml[0]:
            return lml
        elif rml[0]>=lml[0] & rml[0]>=mml[0]:
            return rml
        else:
            return sum(mml)
复制代码

分治法最终经过函数自身的迭代将复杂度为n2的问题简化到nlgn,这其中思惟的难点就是对函数迭代的理解,既要会“分”,也要能“治”,而编程的难点在于迭代过程当中的一些参数取值的范围和边界的细节问题ui

相关文章
相关标签/搜索