使用记忆化与不使用记忆化的递归

2024-04-26

我在学校做的作业是用递归计算加泰罗尼亚数: 第一个没有记忆

def catalan_rec(n):
res = 0
if n == 0:
    return 1
else:
    for i in range (n):
        res += (catalan_rec(i))*(catalan_rec(n-1-i))
    return res

第二个:

def catalan_mem(n, memo = None):
    if memo == None:
        memo = {0: 1}
    res = 0
    if n not in memo:
        for i in range (n):
            res += (catalan_mem(i))*(catalan_mem(n-1-i))
        memo[n] = res
    return memo[n]

最奇怪的事情发生在我身上: 记忆需要两倍的时间!当它应该是相反的时候!

有人可以向我解释一下吗?


这个问题激发了我研究各种加泰罗尼亚数字算法和各种记忆方案的相对速度。下面的代码包含问题中给出的递归算法的函数以及一种仅需要一次递归调用的更简单的算法,这也很容易迭代实现。还有一个基于二项式系数的迭代版本。所有这些算法都在维基百科文章中给出加泰罗尼亚语号码 https://en.wikipedia.org/wiki/Catalan_number.

对于大多数记忆版本来说,获得准确的时间并不容易。通常当使用timeit模块一对要测试的函数执行多个循环,但由于缓存的原因,这里并没有给出真实的结果。为了获得真实的结果需要清除缓存,虽然这可能有点混乱和缓慢,所以缓存清除需要在计时过程之外完成,以避免将缓存清除的开销添加到实际加泰罗尼亚数字的时间上计算。因此,此代码通过简单地计算一个大的加泰罗尼亚数字来生成计时信息,无需循环。

除了计时代码之外,还有一个功能,verify(),它验证所有加泰罗尼亚数字函数产生相同的结果,并且有一个函数可以打印每个加泰罗尼亚数字函数的字节码。这两个函数都已被注释掉。注意verify()填充缓存,因此调用verify() before time_test()会导致计时信息无效。

下面的代码是使用 Python 2.6.6 编写和测试的,但它也可以在 Python 3.6.0 上正确运行。

#!/usr/bin/env python

''' Catalan numbers

    Test speeds of various algorithms

    1, 1, 2, 5, 14, 42, 132, 429, 1430, 4862, 16796, 58786, 208012, 742900, ...

    See https://en.wikipedia.org/wiki/Catalan_number
    and http://stackoverflow.com/q/33959795/4014959

    Written by PM 2Ring 2015.11.28
'''

from __future__ import print_function, division

from timeit import Timer
import dis


#Use xrange if running on Python 2
try:
    range = xrange
except NameError:
    pass

def catalan_rec_plain(n):
    ''' no memoization. REALLY slow! Eg, 26 seconds for n=16 '''
    if n < 2:
        return 1

    res = 0
    for i in range(n):
        res += catalan_rec_plain(i) * catalan_rec_plain(n-1-i)
    return res


#Most recursive versions have recursion limit: n=998, except where noted
cache = {0: 1}
def catalan_rec_extern(n):
    ''' memoize with an external cache '''
    if n in cache:
        return cache[n]

    res = 0
    for i in range(n):
        res += catalan_rec_extern(i) * catalan_rec_extern(n-1-i)
    cache[n] = res
    return res


def catalan_rec_defarg(n, memo={0: 1}):
    ''' memoize with a default keyword arg cache '''
    if n in memo:
        return memo[n]

    res = 0
    for i in range(n):
        res += catalan_rec_defarg(i) * catalan_rec_defarg(n-1-i)
    memo[n] = res
    return res


def catalan_rec_funcattr(n):
    ''' memoize with a function attribute cache '''
    memo = catalan_rec_funcattr.memo
    if n in memo:
        return memo[n]

    res = 0
    for i in range(n):
        res += catalan_rec_funcattr(i) * catalan_rec_funcattr(n-1-i)
    memo[n] = res
    return res

catalan_rec_funcattr.memo = {0: 1}


def make_catalan():
    memo = {0: 1}
    def catalan0(n):
        ''' memoize with a simple closure to hold the cache '''
        if n in memo:
            return memo[n]

        res = 0
        for i in range(n):
            res += catalan0(i) * catalan0(n-1-i)
        memo[n] = res
        return res
    return catalan0

catalan_rec_closure = make_catalan()
catalan_rec_closure.__name__ = 'catalan_rec_closure'


#Simple memoization, with initialised cache
def initialise(memo={}):    
    def memoize(f):
        def memf(x):
            if x in memo:
                return memo[x]
            else:
                res = memo[x] = f(x)
                return res
        memf.__name__ = f.__name__
        memf.__doc__ = f.__doc__
        return memf
    return memoize

#maximum recursion depth exceeded at n=499
@initialise({0: 1})
def catalan_rec_decorator(n):
    ''' memoize with a decorator closure to hold the cache '''
    res = 0
    for i in range(n):
        res += catalan_rec_decorator(i) * catalan_rec_decorator(n-1-i)
    return res

# ---------------------------------------------------------------------

#Product formula
# C_n+1 = C_n * 2 * (2*n + 1) / (n + 2)
# C_n = C_n-1 * 2 * (2*n - 1) / (n + 1)

#maximum recursion depth exceeded at n=999
def catalan_rec_prod(n):
    ''' recursive, using product formula '''
    if n < 2:
        return 1
    return (4*n - 2) * catalan_rec_prod(n-1) // (n + 1)

#Note that memoizing here gives no benefit when calculating a single value
def catalan_rec_prod_memo(n, memo={0: 1}):
    ''' recursive, using product formula, with a default keyword arg cache '''
    if n in memo:
        return memo[n]
    memo[n] = (4*n - 2) * catalan_rec_prod_memo(n-1) // (n + 1)
    return memo[n]


def catalan_iter_prod0(n):
    ''' iterative, using product formula '''
    p = 1
    for i in range(3, n + 2):
        p *= 4*i - 6 
        p //= i 
    return p


def catalan_iter_prod1(n):
    ''' iterative, using product formula, with incremented m '''
    p = 1
    m = 6
    for i in range(3, n + 2):
        p *= m
        m += 4 
        p //= i 
    return p

#Add memoization to catalan_iter_prod1
@initialise({0: 1})
def catalan_iter_memo(n):
    ''' iterative, using product formula, with incremented m and memoization '''
    p = 1
    m = 6
    for i in range(3, n + 2):
        p *= m
        m += 4 
        p //= i 
    return p

def catalan_iter_prod2(n):
    ''' iterative, using product formula, with zip '''
    p = 1
    for i, m in zip(range(3, n + 2), range(6, 4*n + 2, 4)):
        p *= m
        p //= i 
    return p


def catalan_iter_binom(n):
    ''' iterative, using binomial coefficient '''
    m = 2 * n
    n += 1
    p = 1
    for i in range(1, n):
        p *= m
        p //= i
        m -= 1
    return p // n


#All the functions, in approximate speed order
funcs = (
    catalan_iter_prod1,
    catalan_iter_memo,
    catalan_iter_prod0,
    catalan_iter_binom,
    catalan_iter_prod2,

    catalan_rec_prod,
    catalan_rec_prod_memo,
    catalan_rec_defarg,
    catalan_rec_closure,
    catalan_rec_extern,
    catalan_rec_decorator,
    catalan_rec_funcattr,
    #catalan_rec_plain,
)

# ---------------------------------------------------------------------

def show_bytecode():
    for func in funcs:
        fname = func.__name__
        print('\n%s' % fname)
        dis.dis(func)

#Check that all functions give the same results
def verify(n):
    range_n = range(n)
    #range_n = [n]
    func = funcs[0]
    table = [func(i) for i in range_n]
    #print(table)
    for func in funcs[1:]:
        print(func.__name__, [func(i) for i in range_n] == table)

def time_test(n):
    ''' Print timing stats for all the functions '''
    res = []
    for func in funcs:
        fname = func.__name__
        print('\n%s: %s' % (fname, func.__doc__))
        setup = 'from __main__ import cache, ' + fname
        cmd = '%s(%d)' % (fname, n)
        t = Timer(cmd, setup)
        r = t.timeit(1)
        print(r)
        res.append((r, fname))

    ##Sort results from fast to slow
    #print()
    #res.sort()
    #for t, fname in res:
        #print('%s:\t%s' % (fname, t))
        ##print('%s,' % fname)


#show_bytecode()

#verify(50)
#verify(997)

time_test(450)

#for i in range(20):
    #print('%2d: %d' % (i, catalan_iter_binom(i)))

典型结果

catalan_iter_prod1:  iterative, using product formula, with incremented m 
0.00119090080261

catalan_iter_memo:  iterative, using product formula, with incremented m and memoization 
0.001140832901

catalan_iter_prod0:  iterative, using product formula 
0.00202202796936

catalan_iter_binom:  iterative, using binomial coefficient 
0.00141906738281

catalan_iter_prod2:  iterative, using product formula, with zip 
0.00123286247253

catalan_rec_prod:  recursive, using product formula 
0.00263595581055

catalan_rec_prod_memo:  recursive, using product formula, with a default keyword arg cache 
0.00210690498352

catalan_rec_defarg:  memoize with a default keyword arg cache 
0.46977186203

catalan_rec_closure:  memoize with a simple closure to hold the cache 
0.474807024002

catalan_rec_extern:  memoize with an external cache 
0.47812795639

catalan_rec_decorator:  memoize with a decorator closure to hold the cache 
0.47876906395

catalan_rec_funcattr:  memoize with a function attribute cache 
0.516775131226

上述结果是由 2GHz Pentium 4 在最小系统负载下产生的。然而,每次运行之间存在相当大的差异,尤其是对于更快的算法。

正如您所看到的,对于问题中使用的双重递归算法来说,使用缓存的默认参数实际上是一个很好的方法。因此,递归版本的清理版本是:

def catalan_rec(n, memo={0: 1}):
    ''' recursive Catalan numbers, with memoization '''
    if n in memo:
        return memo[n]

    res = 0
    for i in range(n):
        res += catalan_rec_defarg(i) * catalan_rec_defarg(n-1-i)
    memo[n] = res
    return res

然而,它是much使用迭代算法之一更有效,例如catalan_iter_prod1。如果您打算多次调用该函数并且很可能出现重复的参数,那么请使用记忆版本,catalan_iter_memo.

总之,我应该提到,最好避免递归,除非它适合问题域(例如,当使用树等递归数据结构时)。 Python无法执行尾调用消除 https://en.wikipedia.org/wiki/Tail_call并且它施加了递归限制。因此,如果有迭代算法,它几乎总是比递归算法更好的选择。当然,如果您正在学习递归并且您的老师希望您编写递归代码,那么您就没有太多选择。 :)

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用记忆化与不使用记忆化的递归 的相关文章

  • 如何将自定义 CSS 添加到脆皮表单?

    我正在尝试在脆皮表单的帮助下为我的网站创建一个响应式表单 我没有使用引导程序 我想将自定义 CSS 添加到脆皮表单以匹配我的整个网站 HTML
  • 使用 `--pre` 选项时,pip 不匹配预发布版本

    假设您已经发布了两个预发行版 package 0 0 1 dev0 package 0 0 2 dev0 My install requires部分在setup py states package gt 0 0 2 lt 1 0 0 现在
  • 将逻辑回归从 R 迁移到 rpy2

    我正在尝试使用 ryp2 进行逻辑回归 我设法执行它 但不知道如何从结果中提取系数和 p 值 我不想在屏幕上打印这些值 而是创建一个函数来独立使用它们 import rpy2 robjects as ro mydata ro r data
  • 按行中的值选择 pandas 数据框中的列

    我有一个pandas DataFrame列太多 我想选择行中的值等于的所有列0 and 1 所有列的类型是int64我无法通过以下方式选择它们object或其他类型 我怎样才能做到这一点 IIUC 然后你可以使用isin http pand
  • PyDev 无法再调试

    我正在使用 eclipse 4 2 1 和 pydev 2 7 1 以前是 2 6 0 一切都工作正常 直到调试器突然停止工作 它打印 pydev debugger 开始 然后根本不运行程序 而是挂起 根据我在其他问题报告中找到的一些信息
  • 为什么比较匹配的字符串比比较不匹配的字符串更快? [复制]

    这个问题在这里已经有答案了 这里有两个测量值 timeit timeit toto 1234 number 100000000 1 8320042459999968 timeit timeit toto toto number 100000
  • 如何使用 cron 作业运行 python 文件

    您好 我创建了一个 python 文件 例如file example py 该文件将输出 sensex 值 假设该文件在linux系统上的路径为 Desktop downloads file example py 我通常会运行该文件pyth
  • python 和回文

    我最近写了一个循环的方法 usr share dict words并使用我的返回回文列表ispalindrome x 方法 这是一些代码 有什么问题吗 它只会停止 10 分钟 然后返回文件中所有单词的列表 def reverse a ret
  • 如何获取 ndarray 的 x 和 y 维度 - Numpy / Python

    我想知道是否可以分别获取 ndarray 的 x 和 y 维度 我知道我可以使用ndarray shape获取表示维度的元组 但如何在 x 和 y 信息中分离它 先感谢您 您可以使用元组拆包 y x a shape
  • 如何在 Flask-SQLAlchemy 中通过 id 删除记录

    I have users我的 MySql 数据库中的表 这张表有id name and age fields 我怎样才能删除一些记录id 现在我使用以下代码 user User query get id db session delete
  • python seaborn:按色调显示 alpha

    在seaborn中 色调为组设置不同的颜色 我可以设置吗alpha取决于组中的JointGrid 或者甚至在单个数据点上 sns set theme jg sns JointGrid data df sns x x y y hue hue
  • 在组织内部分发我的 python 模块

    我用 python 制作了一些模块 我想将它们分发到我的组织内 这些模块已经存储在BitBucket中 例如 有什么方法可以使用 pip install 来分发它们吗 正确的方法是什么 您可以从 GitHub 进行 pip 安装 并且应该能
  • 当日志在不同进程中发出时,caplog 中的消息为空

    我正在使用 log cli true 运行测试 剧本 import logging import sys from multiprocessing import Process logging basicConfig stream sys
  • 如何编写凯撒密码 Python

    我不知道如何开始编写程序 input input Input the text you would like encrypted def cipher text letter code for i in input number code
  • 从另一个文件执行按钮命令?

    我已经开始开发一个 GUI 系统 在该系统中 我需要从一个文件导入一个函数 以便在按下按钮时在主文件中执行 但每次运行它时 我都会得到 AttributeError partially initialized module Two has
  • 如何防止模块被导入两次?

    在编写python模块时 有没有办法防止它被客户端代码导入两次 就像 c c 头文件一样 ifndef XXX define XXX endif 非常感谢 Python 模块不会被多次导入 仅运行两次 import 不会重新加载模块 如果你
  • Python从更高级别的包导入模块

    这是我的包层次结构 app init py Empty file server py global vars py handlers init py Empty file url1 init py Empty file app1 py ap
  • 按权重分组

    给定以下数据框 import pandas as pd d pd DataFrame Age 18 20 20 56 56 Race A A A B B Response 3 2 5 6 2 Weight 0 5 0 5 0 5 1 2 1
  • 如何修复 Python 中损坏的 utf-8 编码?

    我的字符串是Ni m B T t Thi n s Nh t H nh 我想将其解码为Ni m B T t Thi n s Nh t H nh 我在那个网站上看到可以做到这一点http www enderminh com minh utf8
  • 如何使用 Python 从 Azure Functions 中的辅助线程重定向日志

    我正在使用 Azure 函数运行启动多个线程的 Python 脚本 出于性能原因 一切都按预期工作 但 Azure Functions 日志中仅显示来自 main 线程的信息日志 我在 main 中启动的 辅助 线程中使用的所有日志都不会出

随机推荐

  • 关于冒号的简单C++语法问题

    我刚刚看到一个代码片段 其中有一段我以前从未见过的语法 什么是bool start 1 意思是 我在头文件的类定义中找到了它 struct record char name int refcount 4 unsigned dirty 1 这
  • selenium.common.exceptions.WebDriverException:消息:未知错误:无法使用 ChromeDriver Chrome Selenium 创建 Chrome 进程错误

    我正在尝试编写基本的 python Google Chrome 与 webdriver 交互的代码 但在尝试在浏览器上启动链接时不断遇到相同的错误 这是我的代码 from selenium import webdriver import o
  • php:将变量内容下载为文件

    题主可以吗 我有一个正在执行的脚本 有一次 我在变量中有一大段文本 我可以将其作为可下载文件提供 而不实际将变量内容写入磁盘吗 如果您的意思是让用户单击链接并弹出一个对话框以将某些内容保存为文本文件
  • Graphql 创建两个查询之间的关系。错误无法在初始化之前访问

    我有这个代码 const ProductType new GraphQLObjectType name Product fields id type GraphQLID name type GraphQLString category ty
  • 批处理文件中的 URL 解码

    如何在批处理文件中 urldecode 以下字符串 我需要更改以下内容 http x3a x2f x2f www example com x2f some page x2f some x2f link html to this http w
  • 数组运算符 [] 重载 const 和非 const 版本

    我接到一个任务来实现一个模板数组类 要求之一是重载 运算符 我制作了这两个常量和非常量版本 似乎工作正常 const T operator const unsigned int index const and T operator cons
  • 散列到分组数组中

    我对 ruby 的经验不是很丰富 所以我正在努力格式化一段数据 我有这个哈希 其中包含一些具有相同值的键 例如 key gt value1 key2 gt value2 key3 gt value3 key4 gt value1 key5
  • 如何修补 Eclipse 插件?

    我想修复 eclipse 插件 WTP 的官方插件 中的错误 我在本地更改了源代码 对其进行了调试 一切都很好 现在我想将此更改传播到我的 Eclipse 安装 但我遇到了问题 似乎有不止一种方法可以实现这一目标 例如 这个网站 http
  • 从闪亮发送电子邮件

    我是一位新的 Shiny 用户 我有兴趣创建一个 Web 应用程序 访问者可以在其中填写一些问题 取决于随机 R 数据 并可以提交它们 我的问题是找到通过电子邮件向我发送该信息的方法 例如 每次他们提交数据时 我是一名大学讲师 我认为这是评
  • Mac OS X 上的 scp 问题:scp 不喜欢文件名中的空格,“\”修复不起作用

    我正在尝试使用 scp 在两台 Mac 操作系统 10 6 8 之间传输文件 但它失败了 因为我的目录 文件名中有空格 我无法更改目录 文件名 当我使用 Mac 在终端中工作时 我经常使用 符号来表示空格 然而 在这种情况下 它不起作用 我
  • 以编程方式将网页保存到静态 HTML 文件的最佳方法

    我做的研究越多 前景就越黯淡 我正在尝试使用 Python 进行平面保存或静态保存网页 这意味着将所有样式合并到内联属性 并将所有链接更改为绝对 URL 我尝试过几乎所有免费的转换网站 api 甚至 github 上的库 没有一个是那么令人
  • URL 扩展隐藏:重写与重定向

    我已经阅读了很多问题和答案 但我无法决定哪一个更好或如何使用这些扩展隐藏方式的组合 我想要的是就是有一个像这样的url重写堆栈溢出 那么我还应该做什么才能遵守这些规则 url example com file anyEXT show con
  • 从哪里可以获得 Microsoft.DirectX.dll?

    我不知道从哪里得到它 只是微软的 SDK 吗 如果是 如何将其添加到我的项目 Visual Studios 2010 C 中 托管 DirectX 包装器已过时 不再包含在 DirectX SDK 中 最后一个版本是 2010 年 2 月发
  • null对象的hashcode是如何计算的

    由于 HashMap 和 HashSet 允许空对象 那么这些 空 对象的哈希值是多少 java中如何计算 处理它 openJDK 内置的 HashMap 只是将空引用放入数组中用于保存条目的第一个存储桶中 409 private V pu
  • java中引用和对象有什么区别? [复制]

    这个问题在这里已经有答案了 我有类 GUI 所以我可以创建这样的对象 GUI g1 new GUI 和一个像这样的引用变量 GUI g2 现在据我所知 g2 是一个引用变量 它引用 GUI 类 而 g1 是 GUI 类的对象 g1和g2有什
  • 用户名作为路径

    我希望将用户名作为 URL 的一部分 例如mysite com 用户名 这应该重定向到用户配置文件 我用简介2 http drupal org project profile2 and Pathauto http drupal org pr
  • 未选中选项卡下划线的 TabLayout 颜色

    在此图中 在选项卡布局中 选定的选项卡栏下划线颜色为紫色 并且文本 我搜索未选择的标签栏 但找不到未选择的标签栏下划线 我想在选择某个选项卡时更改颜色 更改未选择的选项卡下划线颜色 如果你知道这件事 你会帮助我吗 在drawable文件夹中
  • React Hook“useState”在函数“setResults”中调用,该函数既不是 React 函数组件,也不是自定义 React Hook 函数

    我试图在一个功能组件中进行 API 调用 该组件是一个反应钩子 并根据结果重新渲染组件的内容 这是代码 调用 API 的组件 function IntegrationDownshift render
  • Delphi:MDI应用程序中的最大化子窗体

    如何最大化仅适合客户区而不适合整个父窗口的子窗口 我不希望子窗口在父窗口的主菜单或其他控件下消失 我有这个代码 procedure WMSIZE var Msg TMessage message WM SIZE procedure TFor
  • 使用记忆化与不使用记忆化的递归

    我在学校做的作业是用递归计算加泰罗尼亚数 第一个没有记忆 def catalan rec n res 0 if n 0 return 1 else for i in range n res catalan rec i catalan rec