簡體   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