用简单的英语来说,递归是什么?


74

递归的想法在现实世界中不是很普遍。因此,对于新手程序员来说似乎有些困惑。虽然,我猜他们逐渐适应了这个概念。那么,对于他们来说,如何轻松地理解这个主意呢?




1
递归是函数可以调用自身的时候。“如果您完全了解名称空间和范围以及如何将参数传递给函数,那么您已经知道了递归。我可以展示示例,但是您应该能够弄清楚它们是如何工作的。” 学生通常不会因为递归而感到困惑,这是因为它令人困惑,而是因为他们对可变范围/命名空间没有足够的了解。在进行递归之前,请确保学生可以正确地跟踪程序,在该程序中,您故意为不同范围的变量赋予了相同的名称,以免混淆它们。
dspyz 2014年


1
要了解递归,您必须先了解递归
Goerman

Answers:


110

为了解释递归,我结合了不同的解释,通常都尝试:

  • 解释这个概念,
  • 解释为什么重要,
  • 解释如何获得它。

首先,Wolfram | Alpha用比Wikipedia更简单的术语来定义它:

通过重复特定的数学运算来生成每个项的表达式。


数学

如果你的学生(或你解释过,从现在开始,我会说学生本人)至少有一些数学背景,他们显然已经通过一系列的学习和他们的观念遇到递归递归性及其递推关系

然后,一个很好的开始方法是演示一个系列,并告诉您递归的含义很简单:

  • 数学函数...
  • ...调用自身以计算与第n个元素相对应的值...
  • ...并定义了一些边界。

通常,您最多会得到一个“呵呵,什么”,因为他们仍然不使用它,或者更有可能只是打a。


编码示例

对于其余部分,它实际上是我在回答附录中针对您所指出的有关指针(坏双关)的问题的详细版本。

在这个阶段,我的学生通常知道如何在屏幕上打印内容。假设我们使用C,他们知道如何使用write或打印单个字符printf。他们还了解控制回路。

我通常会求助于一些重复的简单编程问题,直到他们明白为止:

  • 阶乘操作,
  • 字母打印机
  • 反向字母打印机
  • 求幂操作。

阶乘

阶乘是一个非常简单的数学概念,易于理解,其实现非常接近其数学表示形式。但是,他们可能一开始无法理解。

阶乘运算的递归定义

字母

字母版本很有趣,可以教他们思考递归语句的顺序。像指针一样,它们只会向您随机抛出行。关键是使他们意识到可以通过修改条件仅反转函数中语句的顺序来反转循环。那是打印字母有帮助的地方,因为对他们来说这是视觉效果。只需让他们编写一个函数,该函数将为每个调用打印一个字符,然后递归调用自身以编写下一个(或上一个)字符。

FP爱好者现在可以忽略将内容打印到输出流这一事实……让我们在FP前端不要太烦人。(但是,如果您使用的是具有列表支持的语言,则可以在每次迭代时随意将其连接到列表,并仅打印最终结果。但是通常我以C开头,但是不幸的是,对于此类问题和概念,这并不是最好的选择) 。

求幂

求幂问题稍微困难一些(学习的这个阶段)。显然,该概念与阶乘完全相同,并且没有额外的复杂性……只是您有多个参数。通常这足以使人们感到困惑,一开始就把他们扔掉。

它的简单形式:

求幂运算的简单形式

可以这样表示:

求幂运算的递归关系

更难

一旦在教程中显示并重新实现了这些简单的问题,您就可以进行更困难(但非常经典)的练习:

注意:同样,其中一些确实没有什么困难...它们只是从完全相同的角度或稍有不同的角度解决问题。但是实践是完美的。


帮手

参考

一些阅读从来没有伤害。一开始会很好,他们会感到更加失落。这是一种生长在您身上的东西,它坐在您的脑后,直到有一天您意识到自己终于明白了。然后您回想起您阅读的这些内容。该递归递归在计算机科学递推关系页面上维基百科会为现在要做的。

级别/深度

假设您的学生没有太多的编码经验,请提供代码存根。第一次尝试后,请给他们提供可以显示递归级别的打印功能。打印级别的数值会有所帮助。

堆叠即抽屉图

缩进打印结果(或关卡的输出)也有帮助,因为它可以直观地表示您的程序在做什么,可以打开和关闭抽屉式文件系统或文件系统浏览器中的堆栈上下文。

递归首字母缩写词

如果您的学生已经精通计算机文化,那么他们可能已经在使用名称带有递归首字母缩略词的某些项目/软件。一段时间以来一直是一种传统,特别是在GNU项目中。一些示例包括:

递归:

  • GNU-“ GNU不是Unix”
  • Nagios-“ Nagios不会坚持圣徒时代”
  • PHP-“ PHP超文本预处理器”(和原始的“个人主页”)
  • 葡萄酒-“葡萄酒不是模拟器”
  • Zile-“ Zile有损Emacs”

相互递归:

  • HURD-“ Unix替换守护程序的HIRD”(其中HIRD是“表示深度的接口的HURD”)

让他们尝试提出自己的想法。

同样,有很多递归幽默的出现,例如Google的递归搜索更正。有关递归的更多信息,请阅读此答案


陷阱和进阶学习

人们通常会遇到的一些问题,您需要知道一些答案。

为什么,天哪?

为什么要这么做?一个好的但非显而易见的原因是,用这种方式表达问题通常更简单。一个不太好但是很明显的原因是它通常需要较少的键入(不过,不要仅仅因为使用递归就让他们感到l ...)。

使用递归方法时,某些问题肯定更容易解决。通常,使用分而治之范例可以解决的任何问题都适合多分支递归算法。

又是什么?

为什么我的n(或(不管您的变量名称是什么))每次都不同?初学者通常在理解变量和参数是什么以及程序中如何命名的东西n可能具有不同的值时遇到问题。因此,如果此值在控制循环或递归中,那就更糟了!保持友善,不要在各处使用相同的变量名,并且要明确指出参数只是变量

结束条件

如何确定我的最终状况?这很简单,只要让他们大声说出步骤即可。例如,阶乘从5开始,然后从4开始,然后从...直到0。

细节决定成败

不要和早期邻接说话,例如尾部呼叫优化。我知道,我知道TCO很好,但是起初他们不在乎。给他们一些时间,以对他们有用的方式来围绕这个过程。以后再随意破坏他们的世界,但请稍稍休息一下。

同样,从第一堂课开始就不要直接谈论调用堆栈及其内存消耗,以及... 堆栈溢出。我经常辅导学生私下谁告诉我,他们有50张幻灯片讲讲课一切有了解递归时,他们勉强可以在这个阶段正确地写一个循环。这是一个很好的例子,说明引用以后会有所帮助,但现在只会使您深深困惑

但是,请在适当的时候明确指出有理由走迭代或递归路线

相互递归

我们已经看到,函数可以是递归的,甚至它们可以具有多个调用点(8皇后,河内,斐波那契或什至扫雷器的探索算法)。但是相互递归调用呢?也从这里开始数学。f(x) = g(x) + h(x)其中,g(x) = f(x) + l(x)hl刚做的东西。

仅从数学序列开始,因为合同由表达式明确定义,因此更容易编写和实现。例如,霍夫施塔特女性和男性序列

霍夫施塔特的男女顺序

但是,就代码而言,要指出的是,相互递归解决方案的实现通常会导致代码重复,而应该简化为单个递归形式(请参阅Peter Norvig解决每个数独难题


5
在第5或6次后看到答案后,我正在阅读您的答案。我认为很好,但是吸引其他用户在这里花了太长时间。我在这里学到了很多有关递归教学的知识。作为一个老师,请你评价我的想法教学recursion- programmers.stackexchange.com/questions/25052/...
高尔杉

9
@Gulshan,我认为此答案与所有​​内容一样全面,并且容易被休闲读者“撇开”。因此,它static unsigned int vote = 1;来自我。如果您愿意,请原谅静态幽默:)这是迄今为止最好的答案。
蒂姆·波斯特

1
@古尔桑:只有一个愿意学习的人才愿意花时间去做:)我并不介意。有时,简短的答案很优雅,并传达了许多有用和必要的信息,以开始或解释一般概念。我只是想为该问题提供更长的答案,并且考虑到OP提到了一个我被授予“正确”答案的问题,并提出了类似的答案,因此我认为提供相同类型的答案是适当的。很高兴您学到了一些东西。
haylem 2010年

@Gulshan:现在关于您的答案:第一点我很困惑。我喜欢您将递归函数的概念描述为随着时间的推移会逐渐改变状态的事物,但是我认为您的呈现方式有些奇怪。读完您的3分后,我预计不会有很多学生突然能够解决河内问题。但这可能只是措辞上的问题。
haylem 2010年

我已经很好地证明了相同的变量在不同的递归深度中可以具有不同的值:考虑让学生在遵循一些递归代码的同时写下他们正在执行的步骤以及变量的值。当它们到达递归时,让它们重新开始新的曲调。一旦他们达到退出状态,就让他们回到上一块。这本质上是在模拟调用堆栈,但这是显示这些差异的好方法。
安迪·亨特

58

从同一函数内调用一个函数。


2
这是从头开始的最好解释。简单明了;建立了此摘要后,请进入所有行程详细信息。
2011年

27

递归是一个自我调用的函数。

了解如何使用它,何时使用它以及如何避免不良设计非常重要,这需要您亲自尝试并了解会发生什么。

您需要知道的最重要的事情是要非常小心,以免出现永远不会结束的循环。从pramodc84到您的问题的答案有以下错误:永远不会结束...
递归函数必须始终检查条件以确定是否应再次调用自身。

使用递归的最经典示例是使用没有深度限制的树。这是您必须使用递归的任务。


您仍然可以以迭代方式实现最后一段,尽管递归显然会更好。“知道如何使用它,何时使用它以及如何避免不良设计是很重要的,这需要您自己尝试一下并了解会发生什么。”我认为问题的重点是为这样的事情。
haylem 2010年

@haylem:您对“如何使用它,何时使用它以及如何避免不良设计..”的回答是正确的,这将更符合OP的要求(而不仅仅是“亲自尝试”) ”,正如我所说),但这将需要进行更具教学意义的广泛演讲,而不是在此快速回答问题。不过,您的回答做得很好。为此+1。那些真正需要更好地理解这一概念的人将从阅读您的答案中受益。
敬畏

一对互相调用的函数呢?A呼叫B,然后再次呼叫A直到达到某种条件。是否仍将其视为递归?
santiagozky 2012年

是的,该函数a仍会自行调用,只是间接调用(通过调用b)。
kindall 2012年

1
@santiagozky:正如kindall所说,它仍然是递归。但是,我建议不要这样做。使用递归时,在代码中应该非常清楚递归已到位。如果通过另一个函数间接调用自身,则将很难看到正在发生的事情。如果您不知道某个函数可以有效地调用自身,则可能会遇到以下情况:您(或其他未创建此函数的人)破坏了递归的某些条件(同时更改了代码中的功能),然后结束了陷入无休止的死锁。
2012年

21

递归编程是逐步减少问题以更容易解决其自身版本的过程。

每个递归函数都倾向于:

  1. 列出要处理的列表,其他结构或问题域
  2. 处理当前点/步骤
  3. 在其余/子域上调用自己
  4. 合并或使用子域工作的结果

如果步骤2在3之前,并且步骤4是微不足道的(并置,求和或不执行任何操作),则启用尾递归。第2步通常必须在第3步之后进行,因为可能需要问题子域的结果才能完成当前步骤。

遍历直截了当的二叉树。遍历可以按需要,按顺序或按顺序进行。

   B
A     C

预购:BAC

traverse(tree):
    visit the node
    traverse(left)
    traverse(right)

按顺序:ABC

traverse(tree):
    traverse(left)
    visit the node
    traverse(right)

后期订购:ACB

traverse(tree):
    traverse(left)
    traverse(right)
    visit the node

很多递归问题是映射操作或折叠的特殊情况-仅了解这两个操作可能会导致对递归的良好用例有深入的了解。


实际递归的关键组成部分是使用解决较小问题的解决方案来解决较大问题的想法。否则,您将拥有无限递归。
巴里·布朗

@Barry Brown:完全正确。因此,我的陈述是“ ...将问题简化为更易于解决的问题”
Orbling 2010年

我不一定要这么说……通常是这种情况,尤其是在分而治之的问题中,或者在您真正定义了归结关系而归结为简单情况的情况下。但我想说的更多的是证明问题的每次迭代N,都有一个可计算的情况N +
1。– haylem 2010年

1
@Sean McMillan:递归在适合它的领域中使用时是一个强大的工具。我经常看到它被用作处理一些相对琐碎问题的巧妙方法,这极大地混淆了手头任务的真实本质。
2011年

20

OP表示在现实世界中不存在递归,但我希望有所不同。

让我们以现实世界中切比萨饼的“操作”为例。您已将比萨饼从烤箱中取出,然后将其切成两半,然后将其切成两半,然后再将其切成两半,以供食用。

一遍又一遍地切割比萨饼直到获得所需结果(切片数)的操作。为了争辩,让我们说未切割的比萨饼本身就是一片。

这是Ruby中的示例:

def cut_pizza(existing_slices,desired_slices)
  如果exist_slices =需要的_slices
    #我们没有足够的切片来养活所有人,所以
    #我们正在切割披萨片,因此其数量增加了一倍
    new_slices = existing_slices * 2 
    #这是递归调用
    cut_pizza(new_slices,desired_slices)
  其他
    #我们拥有所需的切片数,因此我们返回
    #在这里而不是继续递归
    返回现有的切片
  结束
结束

比萨= 1#整个比萨,“一片”
cut_pizza(pizza,8)#=>我们将得到8

因此,现实世界中的操作是切比萨饼,递归一遍又一遍地做同样的事情,直到您拥有所需的东西为止。

您会发现可以使用递归函数实现的操作有:

  • 计算多个月的复利。
  • 在文件系统上寻找文件(因为文件系统由于目录而成为树)。
  • 我猜大概所有涉及到树木的工作。

我建议编写一个程序来根据文件名查找文件,并尝试编写一个调用自身的函数,直到找到为止,签名看起来像这样:

find_file_by_name(file_name_we_are_looking_for, path_to_look_in)

因此,您可以这样称呼它:

find_file_by_name('httpd.conf', '/etc') # damn it i can never find apache's conf

在我看来,这仅仅是编程机制,是一种巧妙地消除重复的方式。您可以使用变量来重写它,但这是一个“更精细”的解决方案。没有什么神秘的或困难的。您将编写几个递归函数,它将单击并在编程工具框中显示另一个机械技巧。

额外信用cut_pizza如果您要求它提供不是2的幂的切片(即2或4或8或16),上面的示例将给您一个堆栈级别太深的错误。您可以修改它,以便如果有人要10片它不会永远运行吗?


16

好的,我将尝试保持此简单明了。

递归函数是调用自己的函数。递归函数由三部分组成:

  1. 逻辑
  2. 呼唤自己
  3. 何时终止。

编写递归方法的最佳方法是将尝试编写的方法作为一个简单示例,仅处理要迭代的过程的一个循环,然后将调用添加到方法本身,并在需要时添加终止。最好的学习方法就是像万物一样练习。

由于这是程序员的网站,因此我将避免编写代码,但这是一个很好的链接

如果您开了个玩笑,那么您就有了递归的含义。



4
严格来说,递归不需要终止条件。确保递归终止将限制递归函数可以解决的问题类型,并且有些语义表示类型根本不需要终止。
多纳研究员2010年

6

递归是程序员可以用来对自身进行函数调用的工具。斐波那契数列是如何使用递归的教科书示例。

大多数递归代码(如果不是全部的话)都可以表示为迭代函数,但是它通常很杂乱。其他递归程序的好例子是数据结构,例如树,二进制搜索树,甚至是快速排序。

递归用于减少代码的草率,请记住,它通常较慢并且需要更多的内存。


它是较慢还是需要更多的内存,在很大程度上取决于手头的用途。
2010年

5
斐波那契数列计算是一件很麻烦的事。树遍历是更自然的递归使用。通常,当递归使用得当时,它不会变慢并且不需要更多的内存,因为您必须维护自己的堆栈而不是调用堆栈。
David Thornley 2010年

1
@Dave:我不会对此表示怀疑,但是我认为斐波那契是一个很好的例子。
布莱恩·哈灵顿

5

我喜欢用这个:

您如何步行到商店?

如果您在商店的入口,只需经过它即可。否则,请迈出第一步,然后步行至商店。

包括三个方面至关重要:

  • 琐碎的基本案例
  • 解决一小部分问题
  • 递归解决其余问题

实际上,我们在日常生活中经常使用递归;我们只是不这么想。


这不是递归。可能是将其一分为二:走到商店一半,走另一半。递归。

2
这是递归。这不是分而治之,但这只是递归的一种。图算法(例如路径查找)充满了递归概念。
deadalnix

2
这是递归,但是我认为这是一个糟糕的例子,因为从脑海中将“迈出一步,然后走其余的路到商店”转化为迭代算法太容易了。我觉得这等效于将编写良好的for循环转换为无意义的递归函数。
布莱恩(Brian)

3

我要指出的最好的例子是K&R编写的C编程语言。在那本书(我从内存中引用)中,递归索引页面中的条目(单独)列出了他们谈论递归的实际页面,索引页。


2

Josh K已经提到过Matroshka娃娃。假设您想学习只有最短的娃娃才知道的东西。问题在于您不能真正直接与她交谈,因为她原本生活一个较高的玩偶中,第一个图片位于她的左边。这种结构是这样的(一个娃娃生活在较高的娃娃中),直到最后只剩下最高的一个。

因此,您唯一可以做的就是向最高的玩偶提问。最高的娃娃(谁不知道答案)需要将您的问题传递给较短的娃娃(第一张照片在她的右边)。由于她也没有答案,因此她需要问下一个较短的娃娃。这样,直到消息到达最短的玩偶为止。最矮的娃娃(谁是唯一知道秘密答案的人)将答案传递到下一个更高的娃娃(在她的左边找到),然后将答案传递给下一个更高的娃娃……这将一直持续到答案到达它的最终目的地,那是最高的娃娃,最后...你:)

这就是递归的真正作用。函数/方法会自行调用,直到获得期望的答案。这就是为什么在编写递归代码时,决定递归终止时间非常重要的原因。

不是最好的解释,但希望能有所帮助。


2

递归 n。-一种算法设计模式,其中根据自身定义操作。

典型的例子是找到一个数的阶乘n!。0!= 1,对于任何其他自然数N,N的阶乘是所有小于或等于N的自然数的乘积。因此,6!= 6 * 5 * 4 * 3 * 2 * 1 =720。此基本定义将允许您创建一个简单的迭代解决方案:

int Fact(int degree)
{
    int result = 1;
    for(int i=degree; i>1; i--)
       result *= i;

    return result;
}

但是,请再次检查该操作。6!= 6 * 5 * 4 * 3 * 2 * 1。按照相同的定义,5!= 5 * 4 * 3 * 2 * 1,意味着我们可以说6!= 6 *(5!)。依次为5!= 5 *(4!),依此类推。通过这样做,我们将问题减少为对所有先前操作的结果执行的操作。最终,这将减少到一个称为基本案例的点,在该点上,定义是已知的。在我们的例子中,为0!= 1(在大多数情况下,我们也可以说1!= 1)。在计算中,通常允许我们以非常相似的方式定义算法,方法是调用方法本身并传递较小的输入,从而通过多次递归减少基本情况,从而减少了问题:

int Fact(int degree)
{
    if(degree==0) return 1; //the base case; 0! = 1 by definition
    else return degree * Fact(degree -1); //the recursive case; N! = N*(N-1)!
}

使用三元运算符,可以在许多语言中进一步简化此操作(有时在不提供该运算符的语言中,有时将其视为Iif函数):

int Fact(int degree)
{
    //reads equivalently to the above, but is concise and often optimizable
    return degree==0 ? 1: degree * Fact(degree -1);
}

好处:

  • 自然表达-对于许多类型的算法,这是表达函数的非常自然的方式。
  • 降低的LOC-递归定义函数通常更为简洁。
  • 速度-在某些情况下,取决于语言和计算机体系结构,算法的递归比等效的迭代解决方案要快,这通常是因为在硬件级别进行函数调用比迭代循环所需的操作和内存访问要快。
  • 可分性-许多递归算法具有“分而治之”的思想;运算的结果是对输入的两半进行相同运算的结果的函数。这使您可以在每个级别将工作分成两部分,并且如果可用,可以将另一半分配给另一个“执行单元”进行处理。使用迭代算法通常更难或更不可能。

缺点:

  • 需要理解-您只需“掌握”递归的概念即可了解正在发生的事情,从而编写和维护有效的递归算法。否则,它看起来就像黑魔法。
  • 上下文相关-递归是否是一个好主意,取决于可以就其本身定义算法的优雅程度。虽然可以构建例如递归的SelectionSort,但是迭代算法通常更容易理解。
  • 用RAM访问权来交换调用堆栈-通常,函数调用比高速缓存访​​问便宜,这可以使递归比迭代快。但是,调用堆栈的深度通常会受到限制,这可能会导致递归错误,从而导致迭代算法无法正常工作。
  • 无限递归-您必须知道何时停止。无限迭代也是可能的,但是所涉及的循环结构通常更易于理解和调试。

1

我使用的示例是我在现实生活中遇到的一个问题。您有一个容器(例如打算旅行的大背包),想知道总重量。容器中有两个或三个松散的物品,还有一些其他的容器(例如,东西麻袋。)整个容器的重量显然是空容器的重量加上其中所有物品的重量。对于松散的物品,您可以称重它们;对于杂物袋,您可以对其进行称重,或者您可以说:“每个袋子的重量就是空容器的重量加上其中所有物品的重量”。然后,您继续将容器放入容器中,依此类推,直到到达容器中只有零散物品的地步。那是递归。

您可能会认为这在现实生活中从来没有发生过,但可以想象一下,试图计算或计算特定公司或部门中人员的薪金,这些人员或混合人员只为公司工作,部门中的人员,然后是有部门的部门等等。或在有地区(其中有些地区有次地区等)的国家/地区进行的销售。此类问题在企业中始终存在。


0

递归可用于解决许多计数问题。例如,假设您在一个聚会上有一组n个人(n> 1),并且每个人都一次与其他人握手。进行了几次握手?您可能知道解为C(n,2)= n(n-1)/ 2,但是您可以按以下方式递归求解:

假设只有两个人。那么(通过检查)答案显然是1。

假设您有三个人。挑出一个人,并注意他/她与另外两个人握手。之后,您只需要数一下其他两个人之间的握手。刚才我们已经做过,就是1。所以答案是2 +1 = 3。

假设您有n个人。按照与以前相同的逻辑,它是(n-1)+(n-1个人之间的握手次数)。扩展得到(n-1)+(n-2)+ ... + 1。

表示为递归函数

f(2)= 1
f(n)= n-1 + f(n-1),n> 2


0

在生活中(相对于计算机程序而言),递归很少在我们的直接控制下发生,因为递归会造成混淆。同样,感知往往是关于副作用的,而不是功能上纯净的,因此,如果发生递归,您可能不会注意到它。

递归确实在世界上发生了。很多。

一个很好的例子是水循环(的简化版本):

  • 太阳加热湖
  • 水升入天空形成云
  • 乌云飘到山上
  • 在山上,空气变得太冷而无法保留水分
  • 下雨了
  • 一条河形成
  • 河里的水流进湖里

这是一个循环,使其自身再次发生。它是递归的。

可以递归的另一个地方是英语(通常是人类语言)。您可能一开始可能不认识它,但是我们生成句子的方式是递归的,因为规则允许我们将一个符号实例嵌入到另一个相同符号实例中。

摘自史蒂文·平克(Steven Pinker)的《语言本能》:

如果女孩吃冰淇淋或女孩吃糖果,则男孩吃热狗

这是一个包含其他完整句子的完整句子:

这个女孩吃冰淇淋

这个女孩吃糖果

这个男孩吃热狗

理解完整句子的行为涉及理解较小的句子,较小的句子使用相同的心理欺骗手段来理解为完整的句子。

要从编程角度理解递归,最简单的方法是看一看可以用递归解决的问题,并了解为什么应该递归,以及这意味着您需要做什么。

对于该示例,我将使用最大的通用除数函数,简称gcd。

您有两个数字ab。要找到它们的gcd(假设两者都不为0),您需要检查能否a被整除b。如果是,b则为gcd,否则,您需要检查的gcd b和的其余部分a/b

您已经可以看到这是一个递归函数,因为您有gcd函数调用gcd函数。只是为了敲打它,它在c#中(同样,假设0永远不会作为参数传递):

int gcd(int a, int b)
{   
    if (a % b == 0) //this is a stopping condition
    {
        return b;
    }

    return (gcd(b, a % b)); //the call to gcd here makes this function recursive
}

在程序中,有一个停止条件很重要,否则您的功能将永远重复出现,最终将导致堆栈溢出!

在这里使用递归而不是使用while循环或其他迭代构造的原因是,当您阅读代码时,它会告诉您它正在做什么以及接下来将要发生什么,因此更容易弄清楚它是否正常工作。


1
我发现了水循环示例的迭代过程。第二个语言示例似乎比递归更为分而治之。
Gulshan 2010年

@Gulshan:我会说水循环是递归的,因为它会导致自身重复,就像递归函数一样。这与绘制房间不同,就像您在for循环中那样对多个对象(墙壁,天花板等)执行相同的步骤。该语言示例确实使用了分而治之,但是所谓的“函数”也称其自身对嵌套语句起作用,因此是以这种方式递归的。
马特·艾伦

在水循环中,循环由太阳开始,循环中没有其他元素使太阳再次开始。那么,递归调用在哪里?
Gulshan 2010年

没有递归调用!不是功能。:D它是递归的,因为它导致其自身递归。湖中的水又回到湖中,循环又开始了。如果有其他系统将水注入湖中,那将是迭代的。
马特·艾伦

1
水循环是一个while循环。当然,可以使用递归来表达while循环,但是这样做会破坏堆栈。请不要。
Brian

0

这是递归的真实示例。

让他们想象他们有一个漫画集,您将把它们混合成一大堆。小心-如果他们确实有收藏,当您提起收藏的想法时,他们可能会立即杀死您。

现在,让他们借助本手册对大量未分类的漫画进行分类:

Manual: How to sort a pile of comics

Check the pile if it is already sorted. If it is, then done.

As long as there are comics in the pile, put each one on another pile, 
ordered from left to right in ascending order:

    If your current pile contains different comics, pile them by comic.
    If not and your current pile contains different years, pile them by year.
    If not and your current pile contains different tenth digits, pile them 
    by this digit: Issue 1 to 9, 10 to 19, and so on.
    If not then "pile" them by issue number.

Refer to the "Manual: How to sort a pile of comics" to separately sort each
of the new piles.

Collect the piles back to a big pile from left to right.

Done.

这里的好处是:当它们涉及单个问题时,它们具有完整的“堆栈框架”,并且在地面上都可以看到本地桩。给他们多份手册的打印输出,并在每个桩级上放置一个标记,并在该标记上放置您当前所在的位置(即局部变量的状态),以便您可以在每个“完成”上继续。

这就是递归的基本意义:执行相同的过程,只是越细致就越深入。


-1
  • 如果达到退出条件则终止
  • 做一些改变事物状态的事情
  • 从事物的当前状态开始做一切工作

递归是一种非常简洁的方式来表达必须重复的内容,直到达到某些内容为止。



-2

递归的一个很好的解释实际上是“从自身内部发生的动作”。

考虑一个画家画墙,它是递归的,因为该动作是“从天花板到地板绘制一条带,而不是向右踩小踏板,然后(从天花板到地板绘制一条条纹,而不是向右向一点踩小踏板,(从天花板到地板的条形,而不是从小脚踏板向右移一点,然后(等)))”。

他的paint()函数一遍又一遍地调用自己,以构成更大的paint_wall()函数。

希望这位可怜的画家有某种停止状态:)


6
在我看来,该示例似乎更像是一个迭代过程,而不是递归的。
Gulshan 2010年

@Gulshan:递归和迭代可以做类似的事情,并且通常情况下两者都能很好地工作。功能语言通常使用递归而不是迭代。有更好的递归示例,其中迭代地编写相同的内容很麻烦。
David Thornley 2010年
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.