簡體   English   中英

遞歸函數 - Python

[英]Recursion function - Python

這就是問題:

寫一個遞歸函數f,它產生序列0, 1, 0.5, 0.75, 0.625, 0.6875, 0.65625, 0.671875, 0.6640625, 0.66796875 前兩個術語是0和1,每個其他術語是前兩個術語的平均值。

>>> f(0)
0
>>> f(1)
1
>>> f(2)
0.5
>>> [(i,f(i)) for i in range(10)]
[(0, 0), (1, 1), (2, 0.5), (3, 0.75), (4, 0.625), (5, 0.6875), (6, 0.65625), (7, 0.671875), (8, 0.6640625), (9, 0.66796875)]

這是我的代碼到目前為止,我似乎無法弄明白。 任何幫助/建議將不勝感激。

def f(n):
  if n==0:
    return 0
  if n==1:
    return 1
  else:
    return f(n-2)//f(n-1)

遞歸的情況是錯誤的:

return f(n-2)//f(n-1) # not the formula for the average of two numbers

兩個數字ab的平均值是(a + b)/ 2 所以你可以將你的函數定義為:

def f(n):
    if n==0:
        return 0
    if n==1:
        return 1
    else:
        return (f(n-1)+f(n-2))/2

或者我們可以使它與Python版本無關,如:

def f(n):
    if n==0:
        return 0
    if n==1:
        return 1
    else:
        return 0.5*(f(n-1)+f(n-2))

然后,您可以使用列表推導生成序列,如:

>>> [f(i) for i in range(10)]
[0, 1, 0.5, 0.75, 0.625, 0.6875, 0.65625, 0.671875, 0.6640625, 0.66796875]

或者使用map

>>> list(map(f,range(10)))
[0, 1, 0.5, 0.75, 0.625, 0.6875, 0.65625, 0.671875, 0.6640625, 0.66796875]

因此,對於n > 1U0 = 0U1 = 1Un = (U(n-1) + U(n-2)) / 2

你只需將它作為一個函數逐字翻譯:

def f(n):
    if n == 0:
        return 0
    elif n == 1:
        return 1
    else:
        return (f(n-1) + f(n-2)) / 2

現在用於生成從0nUi序列:

def generate_sequence(n):
    return [f(i) for i in range(n)]

這可以(並且確實應該)通過memoization進行優化。 基本上,您只需將先前計算的結果存儲在字典中(而在這種情況下您可以直接使用列表)。

results = dict()
def memoized_f(n):
    if n in results:
        return results[n]
    else:
        results[n] = f(n)
        return results[n]

這樣,對於每個nf(n)將僅計算一次。

作為獎勵,當memoized_f(n)results字典將f(i)的值保持為從0至少 n

遞歸輔助功能

您可以使用簡單的輔助過程和幾個狀態變量來定義它 - 以下f實現演變了線性迭代過程

def f (n):
  def aux (n, a, b):
    if n == 0:
      return a
    else:
      return aux (n - 1, b, 0.5 * (a + b))
  return aux(n, 0, 1)

print([f(x) for x in range(10)])
# [0, 1, 0.5, 0.75, 0.625, 0.6875, 0.65625, 0.671875, 0.6640625, 0.66796875]

走向通用

或者你可以在我稱之為fibx的整個過程中fibx

from functools import reduce

def fibx (op, seed, n):
  [x,*xs] = seed
  if n == 0:
    return x
  else:
    return fibx(op, xs + [reduce(op, xs, x)], n - 1)

現在我們可以使用fibx實現(例如) fib

from operator import add

def fib (n):
  return fibx (add, [0,1], n)

print([fib(x) for x in range(10)])
# [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

或者我們可以使用fibx和自定義運算符來實現您的f

def f (n):
  return fibx (lambda a,b: 0.5 * (a + b), [0, 1], n)

print([f(x) for x in range(10)])
# [0, 1, 0.5, 0.75, 0.625, 0.6875, 0.65625, 0.671875, 0.6640625, 0.66796875]

浪費了計算

這里的一些答案是遞歸(例如) 0.5 * (f(n-1) + f(n-2)) ,它復制了大量的工作。 大約40的n值比天文學更長( 幾分鍾毫秒相比)來計算,而不是我在這里描述的方法。

在這個例子中看一下樹遞歸fib(5) :看看fib(3)fib(2)是如何重復幾次的? 這歸功於fib計划的天真實施。 在這種特殊情況下,我們可以使用輔助循環函數(如我的答案所示)或使用memoisation(在另一個答案中描述)輕松避免這種重復工作

像這樣的樹遞歸導致O(n 2 ),而我的答案中的線性迭代遞歸是O(n)

https://mitpress.mit.edu/sicp/chapter1/node13.html


生成n的序列

@MadPhysicist提供的另一個答案為單個n輸入值生成一個序列 - 即f(9)將生成前10個值的列表。 然而,由於相同的f(n-1), and f(n-2)`調用,實現同時是復雜和幼稚的並且浪費了大量的計算。

我們的初始方法的微小變化可以在很短的時間內生成相同的序列 - 使用我的代碼f(40)將花費幾分之一秒,而這些錯誤的樹遞歸答案將花費超過2分鍾

粗體變化)

def f (n):
  def aux (n, acc, a, b):
    if n == 0:
      return acc + [a]
    else:
      return aux (n - 1, acc + [a], b, 0.5 * (a + b))
  return aux(n, [], 0, 1)

print(f(9))
# [0, 1, 0.5, 0.75, 0.625, 0.6875, 0.65625, 0.671875, 0.6640625, 0.66796875]

如果您想要一個在單個調用中生成序列的函數,而不必為列表的每個元素調用該函數,則可以在展開堆棧時存儲計算的值:

def f(n, _sequence=None):
    if _sequence is None:
        _sequence = [0] * (n + 1)
    if n == 0 or n == 1:
        val = n
    else:
        f(n - 1, _sequence)
        f(n - 2, _sequence)
        val = 0.5 * (_sequence[n - 1] + _sequence[n - 2])
    _sequence[n] = val
    return _sequence

如果f返回單個值,則這樣做的優點是不需要在相同的值上進行多次遞歸,就像最后使用[f(n) for n in range(...)]

您可以使用@RightLeg建議的更全局的memoization形式來記錄多個調用之間的知識。

與其他解決方案不同,此功能實際上會生成完整的序列。 例如,您的原始示例是:

>>> f(9)
[0, 1, 0.5, 0.75, 0.625, 0.6875, 0.65625, 0.671875, 0.6640625, 0.66796875]

另一個簡單的解決方案可能如下所示

a=0.0
b=1.0
count = 0
def f(newavg, second, count):

    avg = (second+newavg)/2
    print avg

    count=count+1
    if count<8:
        newavg = avg
        f(second, avg, count)
f(a, b, count)

當然這個代碼只輸出到監視器...如果你想將輸出放到一個列表中,只需在遞歸中添加代碼。

還要注意在需要的地方正確縮進。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM