[英]How can I improve my code for calculating the Nth row of Pascals Triangle while using Memoization and Recursion?
我一直在修改递归,并决定使用它来计算Pascals Triangle的行。 我已经成功创建了一个生成Pascals Triangle的函数,该函数适用于n <= 7,但是效率非常低。 我知道生成Pascals Triangle的身份,但我对此并不真正感兴趣。 我想要一些改进下面代码的指导。
在大约n = 7之后,需要很长时间才能计算出来,这让我觉得我实现了我的记忆错误。
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)
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)
dictionary[n] = pasc_list
return pasc_list
a = Pascal(5)
print(a)
print(count)
对于n = 5,范围数已经是4694,而当n = 6时,范围数是75105,这是一个巨大的增长。 因此,如果有人可以帮助我降低示波器的制作速度,我将不胜感激!
要在Python中正确使用memo
,请使用可变的默认参数,通常命名为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
您每次迭代都要调用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次,并且越来越多……这是您的代码是如此缓慢的时候。
备注无法弥补不良的算法。 在这种情况下,它并不能弥补任何问题。 考虑删除了记忆逻辑的代码的清理版本:
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)
(此解决方案,@ thebjorn或@vurmux都无法使用默认的Python堆栈分配达到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
添加到一个函数中只是为了发现它实际上放慢了速度-即不是此优化的理想选择。)
专注于编写一个好的算法,并尽可能地对其进行优化。 然后,研究诸如记忆化之类的技术。 但是要做些计时,看看它是否真的是一场胜利。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.