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中那样的散列查找通常比基于索引的查找慢。你是怎么初始化的