备忘录化如何加快 Python 中阶乘的计算速度,即使没有重复计算

83 阅读3分钟

在 Python 中,使用备忘录化技术可以显著加快阶乘计算的速度,即使没有重复的计算。以下是代码示例:

huake_00066_.jpg

import timeit

fac_mem = {}

def fac(k):
  if k < 2: return 1
  if k not in fac_mem:
    fac_mem[k] = k*fac(k-1)
return fac_mem[k]

def fact(k):
  if k < 2: return 1
  return k*fact(k-1)

if __name__ == "__main__": 
  print timeit.timeit("fac(7)","from __main__ import fac")
  print timeit.timeit("fact(7)","from __main__ import fact")
  print timeit.timeit("fac(70)","from __main__ import fac")
  print timeit.timeit("fact(70)","from __main__ import fact")

输出结果如下:

0.236774399224
0.897065011572
0.217623144304
14.1952226578

从输出结果中可以看出,使用备忘录化技术的阶乘计算速度明显快于普通阶乘计算速度。

起初,有人可能会感到疑惑,因为在代码执行之前,字典是空的,为什么备忘录化仍然会加快阶乘的计算速度?毕竟这两种版本调用的次数都是相同的。

为了进一步探究这个问题,有人对代码进行了一些修改,将字典移到了函数内部,如下所示:

def fac(k):
  fac_mem = {}
  if k < 2: return 1
  if k not in fac_mem:
    fac_mem[k] = k*fac(k-1)
  return fac_mem[k]

此时,输出结果发生了变化:

1.92900857225
0.910026658388
25.5475004875
14.2164999769

可以看出,当字典位于函数内部时,计算速度反而变慢了。

2、解决方案

针对上述问题,我们可以从以下两方面来进行分析:

  1. 备忘录化的持久性: 备忘录化的本质是将计算结果存储起来,以便以后可以快速查询和重用。在上面的代码中,当字典位于函数的外部时,它会在函数的整个生命周期内保持存在。这意味着,第一次执行函数时计算的结果将被存储在字典中,并可以在以后的函数调用中快速查找到。而当字典位于函数的内部时,它只在当前函数调用期间存在。这导致了每次函数被调用时,字典都会被重新创建,并且第一次执行函数时计算的结果无法被存储起来。因此,每次调用函数时都需要重新计算阶乘,从而导致速度变慢。

  2. timeit 的运行方式: timeit 是 Python 中一个用于测量函数执行时间的库。它通过多次执行函数并计算平均执行时间来获得函数的运行时间。在默认情况下,timeit 将函数执行 1000000 次。这意味着,对于备忘录化版本的阶乘计算,只有第一次调用需要实际计算阶乘,而其余 999999 次调用都可以直接从字典中查找结果。因此,timeit 会将第一次计算阶乘的时间与 999999 次查找结果的时间进行平均,从而得到一个非常小的平均执行时间。而对于普通版本的阶乘计算,每次调用函数都需要实际计算阶乘,因此 timeit 会计算所有 1000000 次调用函数的时间,从而得到一个较大的平均执行时间。

综上所述,备忘录化之所以能够加快 Python 中阶乘的计算速度,是因为它将计算结果持久化,并利用 timeit 的运行方式将第一次计算阶乘的时间与后续查找结果的时间进行平均。当字典位于函数的外部时,计算结果可以被持久化,因此只有第一次调用函数需要实际计算阶乘,而其余调用可以快速查询结果。而当字典位于函数的内部时,计算结果无法被持久化,因此每次调用函数都需要重新计算阶乘,从而导致速度变慢。