【问题标题】:Find minimum and maximum with recursion efficiently通过递归有效地找到最小值和最大值
【发布时间】:2019-02-20 03:36:53
【问题描述】:

有没有一种方法可以有效地递归地找到列表中的最小值和最大值?我用 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 minmax using only recursion 的可能重复项。希望我对这个问题的回答对您有所帮助。
  • 一个快速的改进是调用f(l[1:]) 一次 并保存它的结果,而不是两次。

标签: python performance recursion


【解决方案1】:

在 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() 的数字更长的列表(略低于因为交互式循环机制也占用了一些调用堆栈帧)。此限制可能不适用于其他 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

【讨论】:

    【解决方案2】:

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

    【讨论】:

    • 对,只是一个练习。谢谢
    【解决方案3】:

    有一种方法可以做到这一点(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。 如果你想要一个更简单的定义,你可以将一个只接受 ls 的函数包裹在 find_min/find_max 周围,并让该函数调用 find_min(ls, 0)find_max(ls, 0)

    【讨论】:

      【解决方案4】:

      为什么要递归?

      这可以正常工作,并且比最好的递归算法快 10 倍:

      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 )
      

      【讨论】:

      • 注意first,*rest = array复制数组元素,这个语法是Py3特有的。
      猜你喜欢
      • 2023-03-09
      • 2014-07-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-05-07
      • 2020-08-27
      • 2012-10-18
      相关资源
      最近更新 更多