【发布时间】:2018-03-18 15:46:45
【问题描述】:
我突然进入了一个递归语言课程 (sml),递归对我来说还不是物理意义上的。我在想方形瓷砖地板有时是整数乘法的模型或隐喻,或者 Cuisenaire Rods 是加减法的模型或类似物。有没有人可以分享这样的模型?
【问题讨论】:
-
The Wikipedia article 有一些几何递归结构的例子
标签: recursion functional-programming sml
我突然进入了一个递归语言课程 (sml),递归对我来说还不是物理意义上的。我在想方形瓷砖地板有时是整数乘法的模型或隐喻,或者 Cuisenaire Rods 是加减法的模型或类似物。有没有人可以分享这样的模型?
【问题讨论】:
标签: recursion functional-programming sml
想象你是一个现实生活中的魔术师,并且可以复制你自己。你让你的替身离目标更近一步,然后给他(或她)与你一样的命令。
你的替身对他的副本做同样的事情。你看,他也是个魔术师。
当最终副本发现自己在目标处创建时,它无处可去,因此它向其创建者报告。哪个也一样。
最终,您可以得到答案——一英寸也不移动——现在可以轻松地从中创建最终结果。你可以假装不知道所有那些替你做实际工作的双打。 “嗯,”你是在对自己说,“如果我离目标更近一步,并且 已经知道 结果?不是 容易 找到最终答案 然后 ?” *
当然,如果你是替身,你必须向你的创建者报告你的发现。
更多here.
(另外,我想我看到了这个“双打”创建链事件here,虽然我不完全确定)。
* 这就是递归方法解决问题的本质。
我怎么知道我的程序是正确的?如果我简单的小组合步骤产生了一个有效的解决方案,假设它为较小的情况产生了正确的解决方案,我所需要的就是确保它适用于最小的情况——基本情况——然后通过 induction em> 有效性得到证实!
另一种可能性是divide-and-conquer,我们将问题分成两半,这样会更快地到达基本情况。只要组合步骤简单(并且当然保持解决方案的有效性),它就可以工作。在我们的魔术师比喻中,我可以创建自己的两个副本,并在完成后将他们的两个答案合二为一。他们每个人都创建了自己的两个副本,所以这会创建一个魔术师的分支树,而不是像以前那样简单的线。
一个很好的例子是Sierpinski triangle,它是由三个四分之一大小的谢尔宾斯基三角形简单地通过将它们堆叠在它们的角上来构建的。
三个组成三角形中的每一个都是根据相同的配方构建的。
虽然它没有基本情况,因此递归是无限的(无底的;无限的),任何 有限 表示 S.T.大概只会画一个点来代替 S.T.太小了(作为基本情况,停止递归)。
在链接的维基百科文章中有一张很好的图片。
递归绘制 S.T.没有大小限制将永远不会在屏幕上绘制任何东西!对于数学家来说,递归可能很棒,但工程师应该对此更加谨慎。 :)
切换到 corecursion ⁄ 迭代(请参阅链接的答案),我们将首先绘制轮廓,然后绘制内部;所以即使没有大小限制,图片也会很快出现。然后程序会很忙,没有任何明显的影响,但这比空屏幕要好。
【讨论】:
我是从Edsger W. Dijkstra 看到这件作品的;他讲述了他的孩子如何抓住递归:
几年后,一个五岁的儿子向我展示了递归的想法是多么顺利地进入未受破坏的头脑。和我一起走在市中心他突然对我说,爸爸,不是每条船都有救生艇,是吗?我说怎么会? 嗯,救生艇可以有一艘较小的救生艇,但那样就没有了。
【讨论】:
【讨论】:
这很奇怪,除了舞蹈动作是物理的之外,这不是一个物理的例子。那天早上我想到了。我称之为“用拉丁文写成,用希伯来文解决”。嗯?你肯定是在说“嗯?”
我的意思是对递归进行编码通常是从左到右进行的,采用拉丁字母样式:“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 *)
那么您不必在等待更右侧的计算结果时暂停左侧的项目。 “向左跳舞”而不是“向右跳舞”?
【讨论】:
我喜欢这个问题,忍不住要添加答案...
递归是编程的俄罗斯娃娃。我想到的第一个例子更接近于相互递归的例子:
当我们有两个函数A 和B 定义为A 调用B 和B 致电 A。您可以使用网络摄像头非常轻松地进行实验(它也适用于 2 个镜子):
会发生什么?
A) 捕获屏幕 (B)你最终得到了这样的图像(是的,我的网络摄像头完全是垃圾):
“简单”递归或多或少相同,只是只有一个actor(函数)调用自身(A 调用A)
这与@WillNess 的答案或多或少相同,但有一些代码和一些交互性(使用 SO 的 js sn-ps)
假设您是一个非常积极的淘金者,正在寻找黄金,拥有一个非常小的矿山,小到您只能垂直寻找黄金。所以你挖,你检查黄金。如果找到了,就不用再挖了,拿走金子就行了。但如果你不这样做,那意味着你必须更深入地挖掘。所以只有两件事可以阻止你:
因此,如果您想以编程方式(使用递归)编写此代码,可能是这样的:
// 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 变量。
这里的概率足够高,可以避免矿工挖到地核。地球内核之于矿工就像堆栈大小之于程序一样。当矿工来到内核时,他痛苦地死去,当程序超过堆栈大小(导致堆栈溢出)时,它就会崩溃。
编译器/解释器可以进行一些优化,以允许无限级别的递归,如尾调用优化。
【讨论】:
T = F T = F (F T) = F (F (F (F ... ))) 我今天在an answer 看到了。