繁体   English   中英

通过递归有效地找到最大值和最小值

[英]Find minimum and maximum with recursion efficiently

有没有办法有效地递归地找到列表中的最小值和最大值? 我是用python编写的,但是效率非常差,每次调用具有相同列表的函数max和min都非常困难。

def f(l):
    if len(l)==1 : return [l[0],l[0]]
    return [max(l[0],f(l[1:])[0]),min(l[0],f(l[1:])[1])]

l=[1,3,9,-3,-30,10,100]
print(f(l))

输出:[100,-30]

-

您是否有任何改进的想法? 即使不将任何其他变量传递给函数,也可以这样做吗?

在Python中,由于以下原因,递归实现在任何情况下都将比迭代实现慢得多:

  • 通话费用
  • 对象创建,包括 部分清单构建
  • 不在循环中使用某些Python的高效构造,例如for .. in

如果您特别需要执行递归算法,则不能消除前者,但是可以减少对象的构造。 由于每次都会复制所有元素,因此列表构造特别麻烦。

  • 与其在每次迭代中构建一个新列表,不如传递相同的列表和当前索引

    • 在您的函数中,您将构建一次新列表,而不是两次!
  • 您还在每次迭代中进行两个递归调用。 他们每个人还会打两个电话,等等,导致电话总数达到惊人的1+2+4+...+2**(N-1) = 2**N-1 更糟的是,这两个调用完全是多余的,因为它们都产生相同的结果。

  • 由于当前列表元素已被多次使用,因此也可以通过将其缓存在变量中而不是每次都检索来切断一些微秒。

def rminmax(l,i=0,cmin=float('inf'),cmax=float('-inf')):
    e=l[i]

    if e<cmin: cmin=e
    if e>cmax: cmax=e
    if i==len(l)-1:
        return (cmin,cmax)
    return rminmax(l,i+1,cmin,cmax)

另请注意,由于CPython的堆栈大小限制,您将无法处理长度超过sys.getrecursionlimit()稍小的sys.getrecursionlimit()由于交互式循环机制也占用了一些调用堆栈框架,因此sys.getrecursionlimit()稍低)。 此限制可能不适用于其他Python实现。

这是我的机器在样本数据上的一些性能比较:

In [18]: l=[random.randint(0,900) for _ in range(900)]

In [29]: timeit rminmax(l)
1000 loops, best of 3: 395 µs per loop

# for comparison:

In [21]: timeit f(l)    #your function
# I couldn't wait for completion; definitely >20min for 3 runs

In [23]: timeit f(l)    #sjf's function
100 loops, best of 3: 2.59 ms per loop

我不确定为什么要使用递归来找到min和max,因为您可以简单地将列表传递给minmax

def f(l):
  return min(l), max(l)

如果您尝试将其作为递归练习来完成,那么在不将min和max传递给递归调用的情况下,我看不到解决该问题的方法。

def f(l, min_=None, max_=None):
  if not l:
    return min_,max_
  min_ = l[0] if min_ is None else min(l[0], min_)
  max_ = l[0] if max_ is None else max(l[0], max_)
  return f(l[1:], min_, max_)

有一种方法可以做到这一点(而且python中的递归确实非常慢;如果您想要可靠的实现,请参见其他答案)。 从左到右考虑您的递归公式:在每个递归级别上,取列表中当前项目的最小值/最大值,并从下一递归级别返回结果。 然后(对于python> = 2.5,我们可以使用三元运算符):

def find_min(ls, idx):
    return ls[idx] if idx == len(ls) - 1 else min(ls[idx], find_min(ls, idx+1))

find_max是类似的; 您可以将min替换为max 如果您想要一个更简单的定义,则可以在find_min/find_max周围包装仅接受ls的函数,并使该函数调用find_min(ls, 0)find_max(ls, 0)

为什么要递归?

这可以很好地工作,并且比最佳的递归算法快约十倍:

def minMax(array): return min(array),max(array)

为了避免每个递归调用两次,您可以编写如下函数:

def minMax(array):
    first,*rest = array  # first,rest = array[0],array[1:]
    if not rest : return first,first
    subMin,subMax = minMax(rest)
    return min(first,subMin), max(first,subMax)

如果要避免最大递归限制(即,在较大列表上),则可以使用二进制方法将数组分为左右两部分。 这将仅使用log(n)级别的递归(并减少一些处理开销):

def minMax(array):
    size = len(array)
    if size == 1 : return array[0],array[0]
    midPoint = size // 2
    leftMin,leftMax   = minMax(array[:midPoint])
    rightMin,rightMax = minMax(array[midPoint:])
    return min(leftMin,rightMin), max(leftMin,rightMin)

如果您想减少数组创建和函数调用的开销,则可以传递索引并避免使用min(),max()和len()(但随后您将递归用作for循环,这在很大程度上击败了目的):

def minMax(array, index=None):
    index = (index or len(array)) - 1
    item = array[index]
    if index == 0 : return item,item
    subMin,subMax = minMax(array,index)
    if item < subMin: return item,subMax
    if item > subMax: return subMin,item
    return subMin,subMax

您可以将前两者结合起来以减少开销并避免递归限制,但是这会损失一些性能:

def minMax(array, start=0, end=None):
    if end is None : end = len(array)-1
    if start >= end - 1:
        left,right = array[start],array[end]
        return (left,right) if left < right else (right,left)
    middle = (start + end) >> 1
    leftMin,leftMax   = minMax(array, start,middle)
    rightMin,rightMax = minMax(array, middle+1,end)
    return ( leftMin if leftMin < rightMin else rightMin ), \
           ( leftMax if leftMax > rightMax else rightMax )

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM