河内塔的递归解决方案有效,因此如果要将 N 个磁盘从挂钩 A 移动到 C,首先将 N-1 从 A 移动到 B,然后将底部的一个移动到 C,然后再次移动 N- 1个磁盘从B到C。本质上,
hanoi(from, to, spare, N):
hanoi(from, spare, to, N-1)
moveDisk(from, to)
hanoi(spare, to, from, N-1)
很明显 hanoi( _ , _ , _ , 1) 走一步,而 hanoi ( _ , _ , _ , k) 走的步数和 2 * hanoi( _ , _ , _ , k-1) + 1 一样多。所以解决方案长度按 1、3、7、15、... 的顺序增长。这与 (1 << k) - 1 的顺序相同,它解释了您发布的算法中的循环长度。
如果您查看解决方案本身,对于 N = 1,您会得到
FROM TO
; hanoi(0, 2, 1, 1)
0 2 movedisk
对于 N = 2,你得到
FROM TO
; hanoi(0, 2, 1, 2)
; hanoi(0, 1, 2, 1)
0 1 ; movedisk
0 2 ; movedisk
; hanoi(1, 2, 0, 1)
1 2 ; movedisk
对于 N = 3 你得到
FROM TO
; hanoi(0, 2, 1, 3)
; hanoi(0, 1, 2, 2)
; hanoi(0, 2, 1, 1)
0 2 ; movedisk
0 1 ; movedisk
; hanoi(2, 1, 0, 1)
2 1 ; movedisk
0 2 ; movedisk ***
; hanoi(1, 2, 0, 2)
; hanoi(1, 0, 2, 1)
1 0 ; movedisk
1 2 ; movedisk
; hanoi(0, 2, 1, 1)
0 2 ; movedisk
由于解决方案的递归性质,FROM 和 TO 列遵循递归逻辑:如果您在列上取中间条目,则上面和下面的部分是彼此的副本,但数字是置换的。这是算法本身的一个明显结果,它不对挂钩数执行任何算术运算,而只是对它们进行置换。在 N=4 的情况下,中间行位于 x=4(上面标有三颗星)。
现在表达式 (X & (X-1)) 取消设置 X 的最小设置位,因此它映射例如从 1 到 7 的数字,如下所示:
1 -> 0
2 -> 0
3 -> 2
4 -> 0 (***)
5 -> 4 % 3 = 1
6 -> 4 % 3 = 1
7 -> 6 % 3 = 0
诀窍是,因为中间行始终是 2 的精确幂,因此恰好设置了一个位,当您将中间行值(在本例中为 4)添加到行(即 4=0+4、6=2+6)。这实现了“复制”属性,中间行的添加实现了排列部分。表达式 (X | (X-1)) + 1 设置右边有 1 的最低零位,并清除这些位,因此它具有与预期相似的属性:
1 -> 2
2 -> 4 % 3 = 1
3 -> 4 % 3 = 1
4 -> 8 (***) % 3 = 2
5 -> 6 % 3 = 0
6 -> 8 % 3 = 2
7 -> 8 % 3 = 2
至于为什么这些序列实际上会产生正确的挂钩编号,让我们考虑 FROM 列。递归解决方案从 hanoi(0, 2, 1, N) 开始,所以在中间行 (2 ** (N-1)) 你必须有 moveisk(0, 2)。现在根据递归规则,在 (2 ** (N-2)) 你需要有 moveisk(0, 1) 和在 (2 ** (N-1)) + 2 ** (N-2) moveisk ( 1、2)。这会为 from pegs 创建“0,0,1”模式,在上表中以不同的排列可见(检查 0,0,1 的第 2、4 和 6 行以及 0,0 的第 1、2、3 行) ,2 和 1,1,0 的第 5、6、7 行,所有相同模式的置换版本)。
现在,在所有具有这种特性的函数中,它们围绕 2 的幂创建自身的副本但具有偏移量,作者选择了那些产生模 3 正确排列的函数。这并不是一项非常困难的任务,因为三个整数 0..2 只有 6 种可能的排列,并且排列在算法中按逻辑顺序进行。(X|(X-1))+1 不一定与河内问题密切相关,也不需要如此;它具有复制属性并且恰好以正确的顺序产生正确的排列就足够了。