我一直在修改递归,并决定使用它来计算Pascals Triangle的行。我已经成功创建了一个生成Pascals Triangle的函数,该函数适用于n <= 7,但是效率非常低。我知道生成Pascals Triangle的身份,但我对此并不真正感兴趣。我想要一些改进下面代码的指导。
大约n = 7之后,要花很长时间才能计算出来,这让我觉得我的记忆实现错误。
http://127.0.0.1:8001/api/v1/namespaces/default/services/fluentd
对于n = 5,作用域数已经是4694,而当n = 6时作用域数是75105,这是一个巨大的增长。因此,如果有人可以帮助我降低示波器的制作速度,我将不胜感激!
答案 0 :(得分:1)
要在Python中正确使用记忆,请使用可变的默认参数,通常命名为memo
:
count = 0
def Pascal(n, memo={0:[1],1:[1,1]}):
global count
count += 1
pasc_list = []
i = 0
j = i+1
if n in memo:
return memo[n]
else:
pasc_list.append(1)
while j < len(Pascal(n-1)):
pasc_list.append(Pascal(n-1)[i] + Pascal(n-1)[j])
i += 1
j = i+1
pasc_list.append(1)
memo[n] = pasc_list
return pasc_list
a = Pascal(7)
print(a)
print(count)
输出:
c:\srv\tmp> python pascal.py
[1, 7, 21, 35, 35, 21, 7, 1]
70
您还应该将记忆返回作为函数的第一件事:
def Pascal(n, memo={0:[1],1:[1,1]}):
if n in memo:
return memo[n]
global count
count += 1
pasc_list = []
i = 0
j = i+1
pasc_list.append(1)
while j < len(Pascal(n-1)):
pasc_list.append(Pascal(n-1)[i] + Pascal(n-1)[j])
i += 1
j = i+1
pasc_list.append(1)
memo[n] = pasc_list
return pasc_list
输出:
c:\srv\tmp> python pascal.py
[1, 7, 21, 35, 35, 21, 7, 1]
6
答案 1 :(得分:0)
您每次迭代都要调用Pascal函数3次(而且,每个函数都越来越多地调用它...)。因此,您的最终复杂度为O(n!)
。只需存储每个Pascal结果的计算即可:
count = 0
def Pascal(n):
global count
count += 1
pasc_list = []
i = 0
j = i+1
dictionary = {0:[1],1:[1,1]}
if n in dictionary:
return dictionary[n]
else:
pasc_list.append(1)
p = Pascal(n-1) # <-------------------- HERE!!
while j < len(p):
pasc_list.append(p[i] + p[j])
i += 1
j = i+1
pasc_list.append(1)
dictionary[n] = pasc_list
return pasc_list
a = Pascal(7)
print(a)
print(count)
将打印:
[1, 7, 21, 35, 35, 21, 7, 1]
7
对递归非常准确,并且永远不要在循环语句中调用繁重的函数(例如:while j < len(Pascal(n-1)): <-- it is VERY bad idea to write code this way!
)。为什么这么慢?
假设我们正在计算Pascal(3):
在P3中,我们在while
中称为P2。因此,我们为j [0] -index调用P2,在其中调用2次,然后在下一次迭代(j [1] -index)中调用3次。在每个P2迭代中,我们将P1迭代称为3次(对于手动P1迭代,则称为4次)。而且我们对P4重复了整个过程4次,对P5重复了16次,并且越来越多...这是您的代码是如此缓慢的时候。
答案 2 :(得分:0)
记住不能弥补算法的错误。在这种情况下,它并不能弥补任何问题。考虑删除了记忆逻辑的代码的清理版本:
count = 0
def Pascal(n):
global count
count += 1
pasc_list = [1]
if n > 0:
i = 0
j = i + 1
previous = Pascal(n - 1)
while j < len(previous):
pasc_list.append(previous[i] + previous[j])
i += 1
j = i + 1
pasc_list.append(1)
return pasc_list
a = Pascal(10)
print(a)
print(count)
(使用默认的Python堆栈分配,此解决方案,@thebjorn和@vurmux都无法达到Pascal(1000)
。)如果我们对@vurmux的可接受答案进行计时,并在上面进行挖掘,则将每行从0循环到900:
for n in range(900):
print(Pascal(n))
结果相同:
> time python3 no-memoization.py > /dev/null
52.169u 0.120s 0:52.36 99.8% 0+0k 0+0io 0pf+0w
> time python3 memoization.py > /dev/null
52.031u 0.125s 0:52.23 99.8% 0+0k 0+0io 0pf+0w
>
如果我们采用以上的非记忆解决方案,只需添加Python自己的lru-cache
装饰器:
from functools import lru_cache
count = 0
@lru_cache()
def Pascal(n):
# ...
我们获得了显着的(100倍)加速:
> time python3 lru_cache.py > /dev/null
0.556u 0.024s 0:00.59 96.6% 0+0k 0+0io 0pf+0w
>
与@thebjorn(+1)解决方案一样,该解决方案可以正确地重用字典缓存,而不是在每次调用时重新分配它。
重定向到文件时,所有输出均相同。 (很多时候,我将functools:lru-cache
添加到一个函数中只是为了发现它实际上放慢了速度-即不是此优化的理想选择。)
专注于编写一个好的算法并尽可能地对其进行调整。然后,研究诸如记忆化之类的技术。但是要把握时间,看看它是否真的是胜利。