Python 斐波那契序列/数动态规划

Python 斐波那契序列/数动态规划,python,fibonacci,Python,Fibonacci,我正在努力提高我的编程逻辑技能,我正在看一个关于如何处理斐波那契数的课程 在查看了6:34的伪代码后,我写了以下内容: In [14]: def my_fib(x, memo=dict()): ...: if memo.get(x): ...: return memo[x] ...: if x == 1 or x == 2: ...: result = 1 ...: else: ...:

我正在努力提高我的编程逻辑技能,我正在看一个关于如何处理斐波那契数的课程

在查看了
6:34的伪代码后,我写了以下内容:

In [14]: def my_fib(x, memo=dict()):
    ...:     if memo.get(x):
    ...:         return memo[x]
    ...:     if x == 1 or x == 2:
    ...:         result = 1
    ...:     else:
    ...:         result = my_fib(x - 1, memo) + my_fib(x -2, memo)
    ...:     memo[x] = result
    ...:     return result
这很好,但是当我看到视频的最后,当那个家伙辱骂他的python代码时,我发现它和我的略有不同

CS Dojo代码:

In [68]: def fib_dyn_2(x, memo):     
    ...:     if memo[x] is not None:
    ...:         return memo[x]
    ...:     if x == 1 or x == 2:
    ...:         result = 1
    ...:     else:
    ...:         result = fib_dyn_2(x-1, memo) + fib_dyn_2(x-2, memo)
    ...:     memo[x] = result
    ...:     return result
    ...: 
    ...: def fib_memo(x):
    ...:     memo = [None] * (x + 1)
    ...:     return fib_dyn_2(x, memo)
In [5]: %time fib_memo(100)
CPU times: user 99 µs, sys: 128 µs, total: 227 µs
Wall time: 187 µs
Out[5]: 354224848179261915075L
我使用字典来缓存他使用的列表有“轻微”的区别

让我吃惊的是,我的代码似乎快了一点。当获得序列
X>=100
中的数字时,以及运行同一数字多次时,序列就是该数字

i、 e.我的代码:

In [4]: %time my_fib(100)
CPU times: user 70 µs, sys: 44 µs, total: 114 µs
Wall time: 92 µs
Out[4]: 354224848179261915075L
CS Dojo代码:

In [68]: def fib_dyn_2(x, memo):     
    ...:     if memo[x] is not None:
    ...:         return memo[x]
    ...:     if x == 1 or x == 2:
    ...:         result = 1
    ...:     else:
    ...:         result = fib_dyn_2(x-1, memo) + fib_dyn_2(x-2, memo)
    ...:     memo[x] = result
    ...:     return result
    ...: 
    ...: def fib_memo(x):
    ...:     memo = [None] * (x + 1)
    ...:     return fib_dyn_2(x, memo)
In [5]: %time fib_memo(100)
CPU times: user 99 µs, sys: 128 µs, total: 227 µs
Wall time: 187 µs
Out[5]: 354224848179261915075L

问题是哪一个答案“更好”或更理想?

我只是试图验证dict和列表版本之间是否存在显著的性能差异。看起来这两种方法之间只有很小的区别。顺便说一句,注意我还测量了缓存列表的创建。如果我比较一下“unix”time命令的打印时间,我完全看不出有什么区别,但当然这也测量了操作系统加载python解释器所需的时间,因此不太可靠

from datetime import datetime
def fib_cached(n, cache=None):
    if n <= 2:
        return 1
    if cache[n] is None:
        fib_n= fib_cached(n-1, cache) + fib_cached(n-2, cache)
        cache[n]= fib_n
    else:
        fib_n= cache[n]
    return fib_n


n= 950

before= datetime.now()
print(fib_cached(n, cache=[None]*(n+1)))
print(datetime.now() - before)
从日期时间导入日期时间
def fib_缓存(n,缓存=无):

如果n我只是试图验证dict和list版本之间是否存在显著的性能差异。看起来这两种方法之间只有很小的区别。顺便说一句,注意我还测量了缓存列表的创建。如果我比较一下“unix”time命令的打印时间,我完全看不出有什么区别,但当然这也测量了操作系统加载python解释器所需的时间,因此不太可靠

from datetime import datetime
def fib_cached(n, cache=None):
    if n <= 2:
        return 1
    if cache[n] is None:
        fib_n= fib_cached(n-1, cache) + fib_cached(n-2, cache)
        cache[n]= fib_n
    else:
        fib_n= cache[n]
    return fib_n


n= 950

before= datetime.now()
print(fib_cached(n, cache=[None]*(n+1)))
print(datetime.now() - before)
从日期时间导入日期时间
def fib_缓存(n,缓存=无):

如果n虽然记忆版的斐波那契数计算比简单的递归方法要好得多,我建议您检查基于斐波那契数计算的解决方案:


虽然记忆版的斐波那契数计算比简单的递归方法要好得多,但我鼓励您检查基于斐波那契数计算的解决方案:


直觉上,基于列表的回忆录应该比基于词典的快一些。我发现调用的算法和顺序对结果有很大的影响,因此公平比较需要谨慎(例如,使用预分配与追加)

我做了一些比较测试,似乎证实了这一点。您还可以通过在算法中使用的操作/逻辑类型获得显著的性能变化

以下是测试结果(重复100次获得第900个斐波那契数):

以下是功能实现:

def my_fib(x, memo=dict()):
     if memo.get(x):
         return memo[x]
     if x == 1 or x == 2:
         result = 1
     else:
         result = my_fib(x - 1, memo) + my_fib(x -2, memo)
     memo[x] = result
     return result

def fibo(N):
    a = b = 1
    for _ in range(2,N): a,b = b,a+b
    return b

def simpleFibo(N,a=0,b=1):
    if N < 3: return a+b
    return simpleFibo(N-1,b,a+b)

def dynaFibo(N,memo={1:1,2:1}):
    if N not in memo:
        memo[N] = dynaFibo(N-1,memo) + dynaFibo(N-2,memo)
    return memo[N]

def dynaFibo2(N,memo=None):
    if not memo:    memo = [0,1,1]+[0]*N
    if not memo[N]: memo[N] = dynaFibo2(N-1,memo) + dynaFibo2(N-2,memo)
    return memo[N]
以及测试程序

from timeit import timeit
count = 100

N = 990

t= timeit(lambda:my_fib(N,dict()), number=count) # providing dict() to avoid reuse between repetitions
print("my_fib(N)",t)

t= timeit(lambda:fibo(N), number=count)
print("fibo(N)",t)

t= timeit(lambda:simpleFibo(N), number=count) 
print("simpleFibo(N)",t)

t= timeit(lambda:dynaFibo(N,{1:1,2:1}), number=count) # providing dict() to avoid reuse between repetitions
print("dynaFibo(N)",t) 

t= timeit(lambda:dynaFibo2(N), number=count) 
print("dynaFibo2(N)",t)

t= timeit(lambda:binFibo(N), number=count) 
print("binFibo(N)",t)

顺便说一句,我假设你的目标是探索动态规划。否则,对斐波那契函数使用双递归肯定是最糟糕的方法。

直觉上,基于列表的记忆应该比基于字典的略快一些。我发现调用的算法和顺序对结果有很大的影响,因此公平比较需要谨慎(例如,使用预分配与追加)

我做了一些比较测试,似乎证实了这一点。您还可以通过在算法中使用的操作/逻辑类型获得显著的性能变化

以下是测试结果(重复100次获得第900个斐波那契数):

以下是功能实现:

def my_fib(x, memo=dict()):
     if memo.get(x):
         return memo[x]
     if x == 1 or x == 2:
         result = 1
     else:
         result = my_fib(x - 1, memo) + my_fib(x -2, memo)
     memo[x] = result
     return result

def fibo(N):
    a = b = 1
    for _ in range(2,N): a,b = b,a+b
    return b

def simpleFibo(N,a=0,b=1):
    if N < 3: return a+b
    return simpleFibo(N-1,b,a+b)

def dynaFibo(N,memo={1:1,2:1}):
    if N not in memo:
        memo[N] = dynaFibo(N-1,memo) + dynaFibo(N-2,memo)
    return memo[N]

def dynaFibo2(N,memo=None):
    if not memo:    memo = [0,1,1]+[0]*N
    if not memo[N]: memo[N] = dynaFibo2(N-1,memo) + dynaFibo2(N-2,memo)
    return memo[N]
以及测试程序

from timeit import timeit
count = 100

N = 990

t= timeit(lambda:my_fib(N,dict()), number=count) # providing dict() to avoid reuse between repetitions
print("my_fib(N)",t)

t= timeit(lambda:fibo(N), number=count)
print("fibo(N)",t)

t= timeit(lambda:simpleFibo(N), number=count) 
print("simpleFibo(N)",t)

t= timeit(lambda:dynaFibo(N,{1:1,2:1}), number=count) # providing dict() to avoid reuse between repetitions
print("dynaFibo(N)",t) 

t= timeit(lambda:dynaFibo2(N), number=count) 
print("dynaFibo2(N)",t)

t= timeit(lambda:binFibo(N), number=count) 
print("binFibo(N)",t)

顺便说一句,我假设你的目标是探索动态规划。否则,对fibonacci函数使用双重递归肯定是最糟糕的方法。

通过一点实验,我发现您的代码的一个变体在@AlainT的计时方面胜过了所有其他候选者,甚至是迭代的。有两个地方的性能会丢失。首先,这一逻辑:

if memo.get(x):
比简单的更慢:

if x in memo:
因为在一次点击中,您最终会查找两次该值,而不是在下一行中查找一次。然而,这里有一个更实质性的改进:

result = my_fib(x - 1, memo) + my_fib(x - 2, memo)
您已经默认了
memo
参数,为什么要传递它?通过执行以下操作,您可以显著加快计时:

result = my_fib(x - 1) + my_fib(x - 2)
我的返工功能:

def my_fib(x, memo={1:1, 2:1}):
     if x in memo:
         return memo[x]

     memo[x] = result = my_fib(x - 1) + my_fib(x - 2)

     return result

通过一点实验,我发现您的代码的一个变体在@AlainT的计时方面胜过所有其他候选代码,甚至是迭代代码。有两个地方的性能会丢失。首先,这一逻辑:

if memo.get(x):
比简单的更慢:

if x in memo:
因为在一次点击中,您最终会查找两次该值,而不是在下一行中查找一次。然而,这里有一个更实质性的改进:

result = my_fib(x - 1, memo) + my_fib(x - 2, memo)
您已经默认了
memo
参数,为什么要传递它?通过执行以下操作,您可以显著加快计时:

result = my_fib(x - 1) + my_fib(x - 2)
我的返工功能:

def my_fib(x, memo={1:1, 2:1}):
     if x in memo:
         return memo[x]

     memo[x] = result = my_fib(x - 1) + my_fib(x - 2)

     return result

我认为你不能真正回答这个问题。视情况而定。通常,如果确实需要的话,您只需要进行这样的性能优化,因为它们会使代码更加复杂(即使在您的示例中只是一点点),这通常会降低代码的可读性和可维护性,因为主要逻辑可能会“淹没”在技术代码中,所以这种缓存通常是个好主意,如果性能是一个真正的问题,但视频的创建者可能跳过了这一步,只是为了介绍函数式编程的概念,而真正的函数式编程语言就是这样做的(我认为haskell就是这样做的)@jottbe我想你会更喜欢基于可读性的
CS-Dojo
版本,对吗?啊,好吧,对不起,我被误导了。我以为这是关于函数式编程的,但似乎视频处理性能。嗯,奇怪的是,您的解决方案更快,因为像dict中那样的散列查找通常比基于索引的查找慢。你是怎么初始化的