And*_*res 50 language-agnostic algorithm bit-manipulation towers-of-hanoi
当我发现河内塔楼的这种不同寻常的迭代解决方案时,我迷失在互联网上:
for (int x = 1; x < (1 << nDisks); x++)
{
FromPole = (x & x-1) % 3;
ToPole = ((x | x-1) + 1) % 3;
moveDisk(FromPole, ToPole);
}
Run Code Online (Sandbox Code Playgroud)
这篇文章在其中一个答案中也有类似的Delphi代码.
然而,对于我的生活,我似乎无法找到一个很好的解释为什么这个工作.
任何人都可以帮我理解吗?
Ant*_*ima 41
对河内塔的递归解决方案是这样的,如果你想将N个盘子从钉子A移动到C,你先将N-1从A移动到B,然后将底部的一个移动到C,然后再移动N.从B到C的1个磁盘.本质上,
hanoi(from, to, spare, N):
hanoi(from, spare, to, N-1)
moveDisk(from, to)
hanoi(spare, to, from, N-1)
Run Code Online (Sandbox Code Playgroud)
很明显,河内(_,_,_,1)采取一次行动,河内(_,_,_,k)采取与2*河内(_,_,_,k-1)+ 1一样多的动作.所以解决方案长度在序列1,3,7,15 ......中增长.这与(1 << k) - 1的序列相同,它解释了您发布的算法中循环的长度.
如果你看看解决方案本身,你得到N = 1
FROM TO
; hanoi(0, 2, 1, 1)
0 2 movedisk
Run Code Online (Sandbox Code Playgroud)
对于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
Run Code Online (Sandbox Code Playgroud)
你得到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
Run Code Online (Sandbox Code Playgroud)
由于解决方案的递归性质,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
Run Code Online (Sandbox Code Playgroud)
诀窍是,因为中间行总是精确的2的幂,因此只有一个位设置,中间行之后的部分等于它之前的部分,当你将中间行值(在这种情况下为4)添加到行(即4 = 0 + 4,6 = 2 + 6).这实现了"复制"属性,中间行的添加实现了排列部分.表达式(X |(X-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
Run Code Online (Sandbox Code Playgroud)
至于为什么这些序列实际上产生了正确的挂钩数,让我们考虑一下FROM列.递归解决方案以hanoi(0,2,1,N)开始,所以在中间行(2**(N-1))你必须有moveisk(0,2).现在通过递归规则,在(2**(N-2))你需要有moveisk(0,1)和at(2**(N-1))+ 2**(N-2)movingisk( 1,2).这为from pegs创建了"0,0,1"模式,在上表中可以看到不同的排列(检查0,4,1的行2,4和6以及0,0的行1,2,3) ,2,和行5,6,7为1,1,0,相同模式的所有置换版本).
现在,在具有此属性的所有函数中,他们创建自身的副本,围绕2的幂但具有偏移量,作者已选择那些生成模3的正确排列的函数.这不是一项非常困难的任务,因为三个整数0..2中只有6种可能的排列,并且排列在算法中按逻辑顺序进行.(X |(X-1))+ 1不一定与河内问题密切相关或者不一定是; 它足以具有复制属性,并且恰好以正确的顺序产生正确的排列.
antti.huima的解决方案基本上是正确的,但是我想要更严格的东西,而且它太大而不适合评论.开始:
首先注意:在该算法的中间步骤x = 2 N-1处,"from"peg为0,"to"peg为2 N%3.这使得"2" (N-1)%3为"备用的"挂钩.对于算法的最后一步也是如此,因此我们看到作者的算法实际上是一个轻微的"欺骗":他们将磁盘从挂钩0移动到挂钩2 N%3,而不是固定的, - 指定"到"挂钩.这可以改变,没有太多的工作.
最初的河内算法是:
hanoi(from, to, spare, N):
hanoi(from, spare, to, N-1)
move(from, to)
hanoi(spare, to, from, N-1)
Run Code Online (Sandbox Code Playgroud)
插入"from"= 0,"to"= 2 N%3,"备用"= 2 N-1%3,我们得到(抑制%3):
hanoi(0, 2**N, 2**(N-1), N):
(a) hanoi(0, 2**(N-1), 2**N, N-1)
(b) move(0, 2**N)
(c) hanoi(2**(N-1), 2**N, 0, N-1)
Run Code Online (Sandbox Code Playgroud)
这里的基本观察是:在(c)行中,钉子恰好是河内(0,2 N- 1,2 N,N-1)移动2 N-1%3 的钉子,即 它们正好是钉子(a)行加入这一数量.
我声称,当我们运行第(c)行时,"from"和"to"pegs是线(a)相应的钉子移动了2 N-1%3.这是从简单的,更一般的引理得出的在hanoi(a+x, b+x, c+x, N)那里,"从和"到"钉子"正好从x向内移动hanoi(a, b, c, N).
现在考虑这些功能
f(x) = (x & (x-1)) % 3
g(x) = (x | (x-1)) + 1 % 3
为了证明给定的算法有效,我们只需要证明:
这两个都很容易显示.