def hanoi(n,f,v,t):
if n == 0:
pass
else:
hanoi(n-1,f,t,v)
print(f"Move disc from {f} to {t}.")
hanoi(n-1,v,f,t)
hanoi(3,"a","b","c")
我正在研究递归,无法弄清楚这个“河内之塔”功能。它工作正常,但我不知道它在做什么。例如,函数打印的指令之一是:
“将光盘从c移到b。”
但是在我看来{f}只能是“ a”或“ b”?
何时(n = 2)似乎很容易理解,因为它调用(n = 1)打印“将光盘从a移动到b”。
然后移回(n = 2),打印“将光盘从a移到c”。
然后调用(n = 1),打印“将光盘从b移到c”。
但是我不明白n值越大的情况。
答案 0 :(得分:2)
这有助于首先用伪代码表达问题:
move_tower(height_of_tower, "origin", "destination", "helper"):
if height_of_tower is 1:
move disk directly from "origin" to "destination"
else:
move height_of_tower-1 disks from "origin" to "helper" using "destination"
move remaining single disk from "origin" to "destination"
move height_of_tower-1 disks from "helper" to "destination" using "origin"
此伪代码可以立即转换为Python代码。您使用n = 0
的基本情况只是另一种写法,因为对于n = 1,else
块中的两个函数调用都不会执行任何操作(基本情况n = 0只是{{1 }}。
递归函数具有两个重要的属性: 1.首先,它定义了一个带有简单解的基本案例,该基本案例直接给出。 2.否则,它说明了如何从局部解中获得解;为此,可以使用任何函数,包括递归函数本身。唯一重要的是,它使用减少的输入数据进行调用,以便在某些时候命中基准。
关于您的问题:“但是在我看来{f}只能是“ a”或“ b”?……但是我不明白n的较大值会发生什么。”
否,{f}根据情况成为“原点”,“目的地”和“助手”标尺。它可以帮助将函数调用写下一个小的n,例如n = 3,并绘制递归树(一个流行的例子是recursive Fibonacci function)。例如,如果n = 3,则会发生这种情况:
通常,很难真正地“递归思考”。它有助于将您的函数视为常规函数,可以使用任何其他函数来获得所需的结果,包括其自身,但数据集会减少。基本情况可确保它不会永远运行。
答案 1 :(得分:1)
第1行:河内输入n,f,v,t
2:如果n为0,则不做任何事情
3:否则,
4:运行n设为n-1的河内
5:使用f打印一些内容
6:在切换输入的情况下运行河内
7:
8:按照以下步骤运行河内
这只是在这里说明代码以帮助您,而不是告诉您它的确切作用。
这与您提到的河内塔功能有关,here是更详细的信息。它是递归的,因为它在其内部运行函数。
答案 2 :(得分:1)
以3个磁盘Word.run(function (context) {
var range = context.document.getSelection();
var tables = context.document.body.tables.load("items");
var topBorders = [];
return context.sync()
.then(function () {
for (var i = 0; i < tables.items.length; i++) {
topBorders.push(tables.items[i].getBorder("Top").load(['color', 'width']));
}
})
.then(context.sync)
.then(function () {
for (var i = 0; i < topBorders.length; i++) {
range.insertText(topBorders[i].color.toString(), "End");
range.insertText(topBorders[i].width.toString(), "End");
}
})
.then(context.sync)
});
为例,恕我直言,最好的解释是在this blog的图片中:
按图片说明所有步骤(从0到7)。
对于未知的n值,当您具有以下条件时,可能会使用此函数:
答案 3 :(得分:1)
要了解递归,您必须先了解递归。
Imo,将您的头放在任何递归函数上的最好方法是写下(以书面形式,老式风格)某种形式发生的事情。
为什么要用纸?绘制随机的东西比在计算机上容易和快捷。这很简单,但是递归更为复杂,因此将所有内容显式写出可能不切实际。在那种情况下,我喜欢象征性地总结内容,绘制图表等。
第二个最好的例子是使它打印东西。为此,让我们稍微修改一下您的初始代码。
def hanoi(n,_from,,t):
print(f"Hanoi called: n:{n}, f:{f}, v:{v}, t:{t}")
if n == 0:
pass
else:
hanoi(n-1,f,t,v)
print(f"Move disc from {f} to {t}. n:{n}")
hanoi(n-1,v,f,t)
hanoi(3,"a","b","c")
输出结果:
Hanoi called: n:3, f:a, v:b, t:c (4)
Hanoi called: n:2, f:a, v:c, t:b (2)
Hanoi called: n:1, f:a, v:b, t:c (1)
Move disc from a to c. n:1 (1)
Move disc from a to b. n:2 (2)
Hanoi called: n:1, f:c, v:a, t:b (3)
Move disc from c to b. n:1 (3)
Move disc from a to c. n:3 (4)
Hanoi called: n:2, f:b, v:a, t:c (6)
Hanoi called: n:1, f:b, v:c, t:a (5)
Move disc from b to a. n:1 (5)
Move disc from b to c. n:2 (6)
Hanoi called: n:1, f:a, v:b, t:c (7)
Move disc from a to c. n:1 (7)
Process finished with exit code 0
进行比较
请注意,不满足基本条件(n == 0)的第一个调用如何继续“深入”到递归中。达到基本情况后,递归将取消调用的堆栈-您从LAST递归调用中移出(n == 1,第一个移动磁盘)。然后n == 2(第二个移动磁盘)等...
也许并不那么具有讽刺意味,河内的塔楼本身是一个很好的类比递归的工作方式:您实际上将调用(例如,您最终要做的事情)堆叠成一大堆。然后,一旦您完成了新调用的堆积(例如,您已经达到基本情况,此处n == 0),那么您将接听最后放入您的调用堆栈中的所有调用并执行。
编辑:我已经删除了对河内的n == 0调用(因为它们什么也不做,只会使事情变得混乱)。我在括号中添加了与图像上的每个调用(2),(3)等相对应的图像中的步骤。每个#将出现两次-执行一次(例如,“从磁盘移动磁盘...”),然后一次放入调用栈(“ Hanoi调用...。”)。
如您所见,当我们打给河内一个n> 1的电话时,其中一些电话会堆叠在一起(例如(1)(2)(4))。当我们达到n == 0时,我们将逐步将它们堆叠。因此,我们首先执行-> c,即(1),因为该调用最后被堆叠到了递归堆栈上。例如。 (4)首先添加了n == 3,但是在执行它之前,递归迫使我们堆叠另一个n == 2(2)和n == 1(1)的对象。只有到那时,我们才能开始执行对河内的积压呼叫。我们将了解为什么对河内的第一次呼叫在下面标记为(4)而不是(3)。只要确保您正确理解了第一部分即可。
然后我们马上执行a ---> b(即(2))。但是在那一点上n == 2,所以当我们对河内进行该调用时,在继续进行(4)之前,我们在堆栈(3)上添加了另一个调用。由于最后一个被添加,我们立即拆开(3)的堆栈。那么,我们剩下的唯一要从调用堆栈弹出的调用是(4)。由于其n == 3,这将使我们在递归堆栈(6)和(5)上添加更多新调用。同样,我们以相反的顺序拆栈它们(因为(5)在(6)的“顶部”)。使用(6),n == 2,因此我们将向河内添加另一个调用,将是(7)。那时,我们不再向堆栈添加调用,因此我们完全摆脱了递归。