递归-是“分而治之”还是“代码重用”


11

递归 -众所周知-是其中的一个问题-绕回头就像在编程之旅中取得“里程碑”。

但是,当涉及到在现实世界中实际使用它时-仅了解递归的机制还不够-在递归是最合适的解决方案的同时,还必须了解问题的性质。

所以我的问题是...

  • 什么是需要递归解决的“问题模式”
  • 是递归形式的“分而治之”策略还是形式的“代码重用”,或者是一种设计模式
  • 您能否举一个现实世界中的示例为例,其中想到递归作为直接解决方案

-更新-

许多答案都将“实际问题”称为遍历树,阶乘等。我更喜欢“真正的实际问题”-让我举个例子...

我们有一个大的文本夹(大约30 MB的文本作为的链接列表structs),我们需要为全文搜索创建一个索引。我们需要将整个索引保留在内存中,然后每10分钟重新为文本建立索引。

每隔10分钟,我们会将整个文本(两个链接列表,逐行)与新生成的文本块进行比较-以查看更改了哪行-然后我们将仅对该行重新索引-这样我们可以避免不必重新索引整个文本。记住-我们需要找到两个30 MB链表之间的差异点。

我的一位同事提出了一个很棒的程序,该程序使用HEAVY递归比较行-然后收集卡盘在阵列中不同的位置-是的,我知道这听起来很令人困惑-递归如何在这里提供帮助-但它做了。

关键是-他怎么看待大量使用递归可以巧妙地解决这个问题?


如今,大多数计算机都具有GB RAM和TB硬盘空间的30 MB真的很大吗?
JB金

30 MB可能并不大-但是考虑到我们的文本被塞进了那种数据结构中-对PROCESS和DIFF来说确实是一大段文本。
treecoder 2011年

3
“遍历文件夹结构”真正的REAL还不够吗?在您的示例中,我完全看不到递归在这里应该是多么不直观,以及为什么使用它甚至应特别引人注目。就像其他任何算法一样,您的同事设计了一种递归算法。您也可能会问Hoare是如何想到递归解决排序问题的。
康拉德·鲁道夫

2
我是否认为您的意思是“代码重用”更像是“无限次地执行相同系列的操作”?在编写用于其他地方的通用代码的意义上,这与“代码重用”相反。
安迪·亨特

4
但是遍历树是一个“真正的问题”,许多人几乎每天都会遇到。
猎鹰

Answers:


16
  • 什么是需要递归解决的“问题模式”

我不会说存在类似使用递归的问题模式之类的问题。可以递归实现的每个函数也可以迭代地实现,通常是通过压入和弹出堆栈来实现的。

这是表达的问题,也是性能的问题。迭代算法通常具有更好的性能并且更易于优化。但是,递归算法受益于更清晰的表达式,因此通常更易于阅读,理解和实现。

有些事情甚至无法递归表示,例如无限列表。所谓的功能语言在很大程度上依赖于递归,因为它是自然的表达方式。俗话说:“递归编程是正确完成功能编程”。

  • 是递归形式的“分而治之”策略还是形式的“代码重用”,或者是一种设计模式

我不会称其为设计模式。这是表达的问题。有时,递归表达式只是功能更强大,表达能力更强,因此可以生成更好更简洁的代码。

  • 您能否举一个现实世界中的示例为例,其中想到递归作为直接解决方案

任何需要遍历树的东西都将由递归算法正确表达。


7
“通常可以通过推入和弹出堆栈来迭代实现每个可以递归实现的功能。” 毕竟,在使用基于堆栈的内存的语言中,使用递归时,您已经在堆栈中上下推函数数据了。
JAB

仅当您通过机器编译或解释语言时;-)而且,从很高的角度来看,表达式和语言完全独立于机器,硬件和OS,因此不一定有堆栈。
猎鹰

啊,是的,你是绝对正确的。我应该说过“在利用基于堆栈的内存的语言/语言编译器的实现中”。
JAB

一般来说,您也是对的。我不想显得挑剔。
猎鹰

2
无限列表可以表示而无需递归,至少无需递归实现。Python生成器可以做到这一点,就像Icon似乎从Python借来的想法的生成器一样。我不确定F#可以做到这一点,尽管我不确定。基本上,生成器是协作例程(如协作多任务)的一种特殊情况,非常适合于实现惰性列表。每次生成器“产生”结果时,调用方将重新获得控制权,并且生成器将保持空闲状态,直到请求下一个结果。
Steve314 2011年

8

是递归形式的“分而治之”策略还是形式的“代码重用”,或者是一种设计模式

都不行 分而治之使用递归。但是递归并不一定是分而治之,因为后者意味着将一个问题分为两个(或更多)部分并对称地解决每个部分。递归时,您无需执行此操作。

代码重用是完全不相关的,设计模式在更高层次上起作用。例如,即使分而治之(也是比递归更高级的模式)仍不被视为设计模式-而是一种算法模式。

您能否举一个现实世界中的示例为例,其中想到递归作为直接解决方案

树遍历。或更一般而言,图形遍历。值得注意的是,这包括遍历文件夹结构。

当然,任何使用分而治之或动态编程的方法,因为两者自然都以递归的方式表示。


动态编程并不总是自然地表示为递归。实际上,动态编程严格地指的是表格方法-不包括记忆。对此似乎有不同的看法,但“动态编程”中的“编程”实际上是一个数学术语,指的是表格方法(我从MIT开源软件算法课程中摘录的琐事)。因此严格地说,动态编程利用通常最容易表示为简单循环的东西来开发最佳子结构。记忆很可能暗示递归,但不一定。
Steve314 2011年

1
@ Steve314我同意DP的实际实现(无论是在计算机程序中还是在手动方式中)很少使用递归。但是这个想法本质上是基于递归关系的,这只是一个递归公式!–和一个基本案例。
康拉德·鲁道夫

我同意“最优子结构”(最优解决方案具有最优局部解决方案)是一种递归思想。那是递归的数学/计算机科学观点,与实现没有直接关系-但是递归在计算机科学中的作用是重要的一点。很少有算法(可能没有设计模式)是计算机科学中的重要工具-大多数纯粹是要研究的主题,而不是用于研究其他东西的工具。
Steve314 2011年

4
what are the "problem patterns" that call for the solution of recursion

从分形的自相似​​性出发,我想说自相等或自同一性(或称其为自称为)是递归的典型问题模式。即,一个问题可以分为与主要问题具有相同结构的子问题。

在提到的树遍历中,每个子树本身就是一棵完整的树,就像主树一样,并且主树可以是另一棵树内的子树。

因此,我想您的同事发现了索引问题的自平等属性。或者他反过来将问题转化为自我平等的表示。


1
+1表示“一个问题可以分解为与主要问题具有相同结构的子问题”
树编码器2011年

+1和释义:问题的解决方案适用于子层。我在现实世界中的例子是找到有助于“批量”的信用卡费用。会计软件会将个人费用和批量存款存入支票帐户。我的情况在这里可能会成为一个问题,因为stackoverflow对此不太敏感。stackoverflow.com/questions/14719806
Chris K

3

好吧,如果人们尝试将命令式循环转换为功能函数,则可以很容易地理解递归。无论如何,让我们尝试为所有问题提供答案:

什么是需要递归解决的“问题模式”

如果您具有树状结构或算法,则需要递归。如果命令性代码处理堆栈,则需要递归。如果算法中的某个步骤取决于先前的步骤(思考循环),则需要递归。这里需要将其解释为可以使用。

是递归形式的“分而治之”策略还是形式的“代码重用”,或者是一种设计模式

没有。分而治之使用递归,但可以通过堆栈实现。代码重用是指其他内容。设计模式比简单的递归更为复杂。

您能否举一个现实世界中的示例为例,其中想到递归作为直接解决方案

解析以及与树结构有关的所有内容。甚至隐式树结构。


3

如果有一种方法可以简化它,使其很容易,那么这可能是递归起作用的线索。您可以进行排序和搜索,以找到确实存在递归解决方案的示例,例如分别存在合并排序二进制搜索

需要记住的一点是,如何像阶乘一样通过递归来解决一些问题。

对于使用递归的真实示例,可以很容易地以递归方式在书架上查找书籍。我只是看书,如果不是我想要的书,那么我继续下本书。当我找到书或碰到行尾时,我停了下来。可以递归地完成检查一本书并转到下一本书的循环。也许这是一个真实的例子。


2

什么是需要递归解决的“问题模式”

一般来说,解决f(x)= f(g(x))的问题时需要进行递归。除非您对无限递归感到满意,否则g(x)不应求值为x

是递归形式的“分而治之”策略还是形式的“代码重用”,或者是一种设计模式

以上都不是。这只是重复执行相同操作的一种方法,有时是基于输入的变化。就此而言,这个概念比设计模式,代码重用甚至计算机要长得多。

您能否举一个现实世界中的示例为例,其中想到递归作为直接解决方案

析因可以解决斐波那契数列,树遍历和许多其他问题。从函数调用本身的意义上讲,递归不一定是实现这类事情的最佳方法。还有其他方法可能会更希望达到相同的效果(例如,堆栈和循环)。


-1

在编写递归算法时,通常将问题的递归定义转换为代码。然后,您甚至不需要知道它将如何执行。

这就是函数编程中发生的事情。实际上,您指定的是什么(定义)而不是如何指定。换句话说,先定义基础,然后再用子问题定义问题。

例如考虑Factorial算法

  • 定义基数:Factorial(1)= 1;
  • 定义阶乘n:阶乘(n)= n *阶乘(n-1);

然后,当您遇到问题时,您应该考虑是否可以递归定义它,如果可以递归定义它,您几乎已经解决了。

但是,任何递归函数都不应该是递归定义。您可以定义基础并将主要问题的解决方案与子问题的解决方案(定义)关联(定义)。但是对于这种关系,您可能需要一个过程。

一个例子是MergeSort在其中merge可以以涉及的定义或整个阵列到分类子阵列的排序的溶液势在必行过程。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.