【问题标题】:Could I ask for physical analogies or metaphors for recursion?我可以要求递归的物理类比或隐喻吗?
【发布时间】:2018-03-18 15:46:45
【问题描述】:

我突然进入了一个递归语言课程 (sml),递归对我来说还不是物理意义上的。我在想方形瓷砖地板有时是整数乘法的模型或隐喻,或者 Cuisenaire Rods 是加减法的模型或类似物。有没有人可以分享这样的模型?

【问题讨论】:

标签: recursion functional-programming sml


【解决方案1】:

想象你是一个现实生活中的魔术师,并且可以复制你自己。你让你的替身离目标更近一步,然后给他(或她)与你一样的命令

你的替身对他的副本做同样的事情。你看,他也是个魔术师。

当最终副本发现自己在目标处创建时,它无处可去,因此它向其创建者报告。哪个也一样。

最终,您可以得到答案——一英寸也不移动——现在可以轻松地从中创建最终结果。你可以假装不知道所有那些替你做实际工作的双打。 “嗯,”你是在对自己说,“如果我离目标更近一步,并且 已经知道 结果?不是 容易 找到最终答案 然后 ?” *

当然,如果是替身,你必须向你的创建者报告你的发现。

更多here.

(另外,我想我看到了这个“双打”创建链事件here,虽然我不完全确定)。


* 这就是递归方法解决问题的本质。

我怎么知道我的程序是正确的?如果我简单的小组合步骤产生了一个有效的解决方案,假设它为较小的情况产生了正确的解决方案,我所需要的就是确保它适用于最小的情况——基本情况——然后通过 induction em> 有效性得到证实!

另一种可能性是divide-and-conquer,我们将问题分成两半,这样会更快地到达基本情况。只要组合步骤简单(并且当然保持解决方案的有效性),它就可以工作。在我们的魔术师比喻中,我可以创建自己的两个副本,并在完成后将他们的两个答案合二为一。他们每个人都创建了自己的两个副本,所以这会创建一个魔术师的分支树,而不是像以前那样简单的线。


一个很好的例子是Sierpinski triangle,它是由三个四分之一大小的谢尔宾斯基三角形简单地通过将它们堆叠在它们的角上来构建的。

三个组成三角形中的每一个都是根据相同的配方构建的。

虽然它没有基本情况,因此递归是无限的(无底的;无限的),任何 有限 表示 S.T.大概只会画一个点来代替 S.T.太小了(作为基本情况,停止递归)。

在链接的维基百科文章中有一张很好的图片。

递归绘制 S.T.没有大小限制将永远不会在屏幕上绘制任何东西!对于数学家来说,递归可能很棒,但工程师应该对此更加谨慎。 :)

切换到 corecursion ⁄ 迭代(请参阅链接的答案),我们将首先绘制轮廓,然后绘制内部;所以即使没有大小限制,图片也会很快出现。然后程序会很忙,没有任何明显的影响,但这比空屏幕要好。

【讨论】:

  • 非常感谢!这很有帮助,包括将谢尔宾斯基三角形作为“我们不知道”的基本案例。
【解决方案2】:

我是从Edsger W. Dijkstra 看到这件作品的;他讲述了他的孩子如何抓住递归:

几年后,一个五岁的儿子向我展示了递归的想法是多么顺利地进入未受破坏的头脑。和我一起走在市中心他突然对我说,爸爸,不是每条船都有救生艇,是吗?我说怎么会? 嗯,救生艇可以有一艘较小的救生艇,但那样就没有了。

【讨论】:

    【解决方案3】:

    fractals 视为递归:每次都应用相同的模式,但每个数字都不同。

    作为具有分形特征的自然现象,维基百科呈现:

    • 山脉
    • 霜晶
    • DNA
    • ,甚至还有蛋白质。

    【讨论】:

    • 不确定这是否能回答问题。分形(可以说)是数学递归(如斐波那契)的示例,而不是物理模拟。
    • 维基百科提到 Natural phenomena with fractal features 山脉、霜晶、DNA,甚至蛋白质。
    • 我很欣赏这个指针,因为维基百科上的文章有一堆具体的例子。它还提到了自相似性,这是搜索物理案例的一个很好的标准
    【解决方案4】:

    这很奇怪,除了舞蹈动作是物理的之外,这不是一个物理的例子。那天早上我想到了。我称之为“用拉丁文写成,用希伯来文解决”。嗯?你肯定是在说“嗯?”

    我的意思是对递归进行编码通常是从左到右进行的,采用拉丁字母样式:“Def fac(n) = n*(fac(n-1))”。运动风格是“从最外到基本情况”。

    但是(请检查我)至少在这个简单的情况下,似乎最简单的评估方法是从右到左,采用希伯来字母风格:从基本情况开始,向外移动到最外层案例:

                                                       (fac(0) = 1)
                                          (fac(1) = 1)*(fac(0) = 1)
                                 (fac(2))*(fac(1) = 1)*(fac(0) = 1)
           (fac(n)*(fac(n-1)*...*(fac(2))*(fac(1) = 1)*(fac(0) = 1)
    (*  Easier order to calculate <<<<<<<<<<< is leftwards, 
        base outwards to outermost case;
        more difficult order to calculate >>>>>> is rightwards, 
        outermost case to base  *)
    

    那么您不必在等待更右侧的计算结果时暂停左侧的项目。 “向左跳舞”而不是“向右跳舞”?

    【讨论】:

    • 非常好!确实存在二元性,我试图在链接的“Prolog 累加器”答案中探索它;在 Wikipedia 的“corecursion”文章中也暗示“与'时间箭头'相反”。递归首先进入基本情况(即从左到右),然后在返回的途中从右到左收缩计算结果。从递归的基本情况开始,累加器的迭代只是从右到左。
    【解决方案5】:

    我喜欢这个问题,忍不住要添加答案...

    递归是编程的俄罗斯娃娃。我想到的第一个例子更接近于相互递归的例子:

    相互递归日常示例

    当我们有两个函数AB 定义为A 调用BB 致电 A。您可以使用网络摄像头非常轻松地进行实验(它也适用于 2 个镜子):

    1. 使用 VLC 或任何可以做到这一点的软件在屏幕上显示网络摄像头输出。
    2. 将网络摄像头对准屏幕。
    3. 屏幕将逐步显示无限的屏幕“漩涡”。

    会发生什么?

    • 网络摄像头 (A) 捕获屏幕 (B)
    • 屏幕显示网络摄像头捕获的图像(屏幕本身)。
    • 网络摄像头捕获屏幕并显示屏幕。
    • 屏幕显示该图像(现在显示两个屏幕)
    • 等等。

    你最终得到了这样的图像(是的,我的网络摄像头完全是垃圾):

    “简单”递归或多或少相同,只是只有一个actor(函数)调用自身(A 调用A

    “简单”递归

    这与@WillNess 的答案或多或少相同,但有一些代码和一些交互性(使用 SO 的 js sn-ps)

    假设您是一个非常积极的淘金者,正在寻找黄金,拥有一个非常小的矿山,小到您只能垂直寻找黄金。所以你挖,你检查黄金。如果找到了,就不用再挖了,拿走金子就行了。但如果你不这样做,那意味着你必须更深入地挖掘。所以只有两件事可以阻止你:

    1. 找到一些金块。
    2. 地球沸腾的铁水内核。

    因此,如果您想以编程方式(使用递归)编写此代码,可能是这样的:

    // This function only generates a probability of 1/10
    function checkForGold() {
      let rnd = Math.round(Math.random() * 10);
      return rnd === 1;
    }
    
    function digUntilYouFind() {
      if (checkForGold()) {
        return 1; // he found something, no need to dig deeper
      }
      // gold not found, digging deeper
      return digUntilYouFind();
    }
    
    let gold = digUntilYouFind();
    console.log(`${gold} nugget found`);
    

    或者增加一点交互性:

    // This function only generates a probability of 1/10
    function checkForGold() {
      console.log("checking...");
      let rnd = Math.round(Math.random() * 10);
      return rnd === 1;
    }
    
    function digUntilYouFind() {
      if (checkForGold()) {
        console.log("OMG, I found something !")
        return 1;
      }
      try {
        console.log("digging...");
        return digUntilYouFind();
      } finally {
        console.log("climbing back...");
      }
    }
    
    let gold = digUntilYouFind();
    console.log(`${gold} nugget found`);

    如果我们没有找到一些金子,digUntilYouFind 函数会调用自身。当矿工从他的矿井“爬回来”时,实际上是对函数的最深子调用,该函数通过其所有父级(调用堆栈)返回金块,直到可以将值分配给 gold 变量。

    这里的概率足够高,可以避免矿工挖到地核。地球内核之于矿工就像堆栈大小之于程序一样。当矿工来到内核时,他痛苦地死去,当程序超过堆栈大小(导致堆栈溢出)时,它就会崩溃。

    编译器/解释器可以进行一些优化,以允许无限级别的递归,如尾调用优化。

    【讨论】:

    • 我喜欢“爬回来”的路线。当其他人描述递归时,这通常是隐藏的,但它是如此重要。
    • 来到这里添加一些关于"Russian matryoshka doll" 的内容,并找到了以它开头的答案。 :) 稍后会阅读。 :) --- 原因:Haskell 的T = F T = F (F T) = F (F (F (F ... ))) 我今天在an answer 看到了。
    猜你喜欢
    • 1970-01-01
    • 2016-11-07
    • 1970-01-01
    • 1970-01-01
    • 2023-03-13
    • 2022-06-25
    • 1970-01-01
    • 1970-01-01
    • 2020-12-24
    相关资源
    最近更新 更多