Python 为什么此回忆录功能不在线性时间内运行?

Python 为什么此回忆录功能不在线性时间内运行?,python,runtime,big-o,time-complexity,memoization,Python,Runtime,Big O,Time Complexity,Memoization,我尝试在递归斐波那契函数中使用数组实现回忆录,fibmem()期望运行时间为O(n)。起初,它看起来就像我有它一样,因为它比常规递归斐波那契函数运行要快得多。(红色表示常规的fib(),绿色表示fibmem()) 但经进一步检查,(fibmem()用红色表示) 看起来好像fibmem()在O(某个常量^n)时间内运行。代码如下: memo = [0] * 100 #initialise the array argument = sys.argv[1] def fibmem(n):

我尝试在递归斐波那契函数中使用数组实现回忆录,
fibmem()
期望运行时间为O(n)。起初,它看起来就像我有它一样,因为它比常规递归斐波那契函数运行要快得多。(红色表示常规的
fib()
,绿色表示
fibmem()

但经进一步检查,(
fibmem()
用红色表示)

看起来好像
fibmem()
在O(某个常量^n)时间内运行。代码如下:

memo = [0] * 100 #initialise the array
argument = sys.argv[1]

def fibmem(n):

        if n < 0:
            return "NO" 
        if n == 0 or n == 1:
            memo[n] = 1
            return memo[n]
        if n not in memo:
            memo[n] = fibmem(n-1) + fibmem(n-2)
        return memo[n]

但我认为我使用数组的实现是类似的。我就是不明白为什么
fibmem()
的数组实现以指数时间运行。发生什么事?我该如何着手解决问题?

您的问题是,中的
操作符在列表上从头到尾扫描列表。它不会神奇地知道你正在有序地存储东西

您可以使用内置此功能的集合来解决此问题。或者,您可以使用数组查找来检查是否设置了数组值:

memo = [1,1] + [0]*98
def fibmem(n):
    answer = memo[n]

    if answer == 0:
        answer = fibmem(n-1) + fibmem(n-2)
        memo[n] = answer

    return answer

真正的问题不在于
中的操作符扫描列表并花费线性时间,而在于您的操作完全是错误的

您的
备忘录将用
[1,1,2,3,5,8,13,21,34,55,89,144,…]填充
。因此,当
n
例如为40,并且您因此检查
40不在备忘录中时,它将总是失败,因为40不是斐波那契数。很明显,你的意思是检查第40个斐波那契数是否已经计算过了,但这根本不是你真正要检查的。而是检查40是否是(已计算的)斐波那契数

因此,只有当
n
本身恰好是一个斐波那契数时,才能得到一个快捷方式,例如34。但在55岁之前,你永远不会有这样的捷径,有效地完全禁用了你的记忆功能(在这些范围内)。这就是为什么会出现指数行为,就像之前的非记忆版本一样

还要注意的是,在n=35和n=36之间曲线的显著中断。这不仅仅是侥幸,因为34是斐波那契数。n=36的情况可以追溯到n=35和n=34,因为n=34是一个即时快捷方式,所以只有n=35部分涉及实际工作。这就是为什么n=36与n=35所用的时间几乎完全相同的原因(当你测量它时,是一个侥幸,它所用的时间略小于

如果备忘录[n]==0:
如果备忘录[n]=0:
不在备忘录[n]:
中,则应检查


或者使用字典:
memo={}
。然后,您的
如果n不在memo:
中,则执行它应该执行的操作(因为它检查键,而不是值)。这还具有不受限制的优点。

中的
运算符对于列表是线性的。把它们另外放在一套里,我不太明白。你把它们另外放在一个集合中是什么意思?@imgoingmad你可以把它添加到集合中,也可以把它添加到集合中,并且只检查集合中的成员身份。但是有更简单的方法,比如把它存储在字典里,或者检查列表的长度等等。你的解决方案是一个非常糟糕的主意。最好从列表为空开始,改为执行
.get(n,0)
。列表没有.get()方法。但是列表索引比散列更快。你能解释一下为什么你认为这个解决方案是一个坏主意吗?是的,这很有效。谢谢谢谢L3viathan提供的额外信息。@AustinHastings你是对的,不是
get
,而是检查尺寸。我认为用固定大小初始化数组是一个坏主意,因为它在100后会中断,并且需要所有的空间,即使N很小。除此之外,你的解决方案是好的。一个集合只是一个未索引值的集合;您需要使用
dict
将以前看到的输入映射到其计算值。您遗漏了有关如何修复它的任何说明。大概不是在备忘录中检查
n
他只需要检查
n
@BrenBarn,我想我认为修复它是琐碎无聊的部分:-P。但我现在补充了这一点。
memo = [1,1] + [0]*98
def fibmem(n):
    answer = memo[n]

    if answer == 0:
        answer = fibmem(n-1) + fibmem(n-2)
        memo[n] = answer

    return answer