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