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

时间:2019-02-20 03:36:53

标签: python performance recursion

是否可以有效地递归地找到列表中的最大值和最小值?我是用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]

-

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

4 个答案:

答案 0 :(得分:1)

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

  • 通话费用
  • 对象创建,包括部分列表构建
  • 不使用for .. in循环之类的Python高效结构

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

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

    • 在您的函数中,您将一次构造一个新列表,而不是两次。
  • 您还将在每次迭代中进行两次 递归调用。他们每个人还会打两个电话,等等,导致总数达 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()的数字(略低的数字,因为交互式循环机制也占用了一些调用堆栈框架) 。此限制可能不适用于其他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

答案 1 :(得分:0)

我不确定您为什么要使用递归来查找最小值和最大值,因为您只需将列表传递给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_)

答案 2 :(得分:0)

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

$(function() {
    {% for c in custs %}
        var name = '{{ c.name }}';
        var age = '{{ c.age }}';
        $('#table1 > tbody').append('<tr><td>' + name + '</td><td>' + age + '</td></tr>');
    {% endfor %}
});

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的函数包装在ls周围,并使该函数调用find_min/find_maxfind_min(ls, 0)

答案 3 :(得分:-2)

为什么递归?

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

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 )