使用记忆和递归时,如何改进计算帕斯卡三角形第N行的代码?

时间:2019-05-20 18:46:19

标签: python recursion memoization

我一直在修改递归,并决定使用它来计算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,这是一个巨大的增长。因此,如果有人可以帮助我降低示波器的制作速度,我将不胜感激!

3 个答案:

答案 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添加到一个函数中只是为了发现它实际上放慢了速度-即不是此优化的理想选择。)

专注于编写一个好的算法并尽可能地对其进行调整。然后,研究诸如记忆化之类的技术。但是要把握时间,看看它是否真的是胜利。