我正在尝试提高编程逻辑技能,并且正在观看videos之一中有关如何计算斐波那契数的信息。
在查看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)
我使用字典进行缓存,而他使用列表进行缓存。
让我兴奋的是,我的代码似乎更快了一点。当获得序列X >= 100
中的数字时,以及运行相同数字时,序列均不止一次。
即我的代码:
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 [5]: %time fib_memo(100)
CPU times: user 99 µs, sys: 128 µs, total: 227 µs
Wall time: 187 µs
Out[5]: 354224848179261915075L
问题是哪个“更好”或更希望作为答案?
答案 0 :(得分:1)
虽然斐波那契数字的记忆式版本比朴素的递归方法要好得多,但我建议您根据Matrix Form的斐波那契数字来检查解决方案:
答案 1 :(得分:0)
我只是试图验证dict和列表版本之间是否存在明显的性能差异。看起来这两种方法之间只有很小的差异。顺便说一句。请注意,我还测量了缓存列表的创建。如果我比较“ unix”时间命令的打印时间,我根本不会发现任何差异,但是当然,这也可以测量操作系统加载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)
答案 2 :(得分:0)
直觉上,基于列表的记忆应该比基于字典的记忆稍快。我发现调用的算法和顺序对结果有很大影响,因此公平比较需要谨慎(例如使用预分配与附加)
我进行了一些比较测试,似乎证实了这一点。您还可以通过算法中使用的运算/逻辑获得显着的性能差异。
以下是测试结果(重复100次以获得第900个斐波那契数):
my_fib(N) 0.0578 Original
fibo(N) 0.0089 Iterative algorithm
simpleFibo(N) 0.0248 Single recursion algorithm
dynaFibo(N) 0.0463 Double recursion with dictionary based memoization
dynaFibo2(N) 0.0440 Double recursion with list based memoization
binFibo(N) 0.0012 Iterative exponential algorithm
(this one responds in O(log(N)) time)
以下是函数实现:
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]
EDIT (添加了指数算法,可以在O(log(N))时间内响应)
def binFibo(N):
a,b = 0,1
f0,f1 = 1,1
r,s = (1,1) if N&1 else (0,1)
N //=2
while N > 0:
a,b = f0*a+f1*b, f0*b+f1*(a+b)
f0,f1 = b-a,a
if N&1: r,s = f0*r+f1*s, f0*s+f1*(r+s)
N //= 2
return r
测试步骤
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)
顺便说一句,我假设您的目标是探索动态编程。否则,对斐波那契函数使用双递归无疑是最糟糕的方法。
答案 3 :(得分:0)
进行了一些实验,我发现您的代码有一个变种,它在@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