Towers of Hanoi问题是递归的典型问题。在其中一个上有3个带磁盘的挂钉,您必须按照给定的规则将所有磁盘从一个挂钩移动到另一个挂钩。您还必须使用最少的移动次数执行此操作。
这是一个解决问题的递归算法:
void Hanoi3(int nDisks, char source, char intermed, char dest)
{
if( nDisks > 0 )
{
Hanoi3(nDisks - 1, source, dest, intermed);
cout << source << " --> " << dest << endl;
Hanoi3(nDisks - 1, intermed, source, dest);
}
}
int main()
{
Hanoi3(3, 'A', 'B', 'C');
return 0;
}
现在,想象同样的问题,只有4个钉子,所以我们添加另一个中间挂钩。当面临必须在任何一点选择哪个中间挂钩的问题时,我们将选择最左边的一个,以防1个以上是免费的。
我有这个问题的递归算法:
void Hanoi4(int nDisks, char source, char intermed1, char intermed2, char dest)
{
if ( nDisks == 1 )
cout << source << " --> " << dest << endl;
else if ( nDisks == 2 )
{
cout << source << " --> " << intermed1 << endl;
cout << source << " --> " << dest << endl;
cout << intermed1 << " --> " << dest << endl;
}
else
{
Hanoi4(nDisks - 2, source, intermed2, dest, intermed1);
cout << source << " --> " << intermed2 << endl;
cout << source << " --> " << dest << endl;
cout << intermed2 << " --> " << dest << endl;
Hanoi4(nDisks - 2, intermed1, source, intermed2, dest);
}
}
int main()
{
Hanoi4(3, 'A', 'B', 'C', 'D');
return 0;
}
现在,我的问题是如何推广这种递归方法来处理K
钉?递归函数会收到一个char[]
,它会保存每个堆栈的标签,所以函数看起来像这样:
void HanoiK(int nDisks, int kStacks, char labels[]) { ... }
我知道Frame-Stewart算法,它最有可能是最优的但未经过验证,并且可以为您提供数量的移动。但是,我对一个严格的递归解决方案感兴趣,该解决方案遵循3和4个挂钩的递归解决方案的模式,这意味着它打印实际的移动。
至少对我来说,维基百科上提出的Frame-Stewart算法的伪代码是相当抽象的,我没有成功地将其转换成打印动作的代码。我会接受它的参考实现(对于随机k
),甚至更详细的伪代码。
我尝试提出某种算法来相应地置换标签数组,但我没有运气让它起作用。任何建议都表示赞赏。
更新
这似乎在功能语言中更容易解决。 这是基于LarsH的Haskell解决方案的F#实现:
let rec HanoiK n pegs =
if n > 0 then
match pegs with
| p1::p2::rest when rest.IsEmpty
-> printfn "%A --> %A" p1 p2
| p1::p2::p3::rest when rest.IsEmpty
-> HanoiK (n-1) (p1::p3::p2::rest)
printfn "%A --> %A" p1 p2
HanoiK (n-1) (p3::p2::p1::rest)
| p1::p2::p3::rest when not rest.IsEmpty
-> let k = int(n / 2)
HanoiK k (p1::p3::p2::rest)
HanoiK (n-k) (p1::p2::rest)
HanoiK k (p3::p2::p1::rest)
let _ =
HanoiK 6 [1; 2; 3; 4; 5; 6]
并且没有将3个钉子视为边缘案例:
let rec HanoiK n pegs =
if n > 0 then
match pegs with
| p1::p2::rest when rest.IsEmpty
-> printfn "%A --> %A" p1 p2
| p1::p2::p3::rest
-> let k = if rest.IsEmpty then n - 1 else int(n / 2)
HanoiK k (p1::p3::p2::rest)
HanoiK (n-k) (p1::p2::rest)
HanoiK k (p3::p2::p1::rest)
请注意,这不能处理没有解决方案的退化情况,例如HanoiK 2 [1; 2]
答案 0 :(得分:16)
这是Haskell中的一个实现(更新:通过在r = 3时使k = n-1来处理3-peg情况):
-- hanoi for n disks and r pegs [p1, p2, ..., pr]
hanoiR :: Int -> [a] -> [(a, a)]
-- zero disks: no moves needed.
hanoiR 0 _ = []
-- one disk: one move and two pegs needed.
hanoiR 1 (p1 : p2 : rest) = [(p1, p2)] -- only needed for smart-alecks?
{-
-- n disks and 3 pegs -- unneeded; covered by (null rest) below.
hanoiR n [p1, p2, p3] =
hanoiR (n - 1) [p1, p3, p2] ++
[(p1, p2)] ++
hanoiR (n - 1) [p3, p2, p1]
-}
-- n disks and r > 3 pegs: use Frame-Stewart algorithm
hanoiR n (p1 : p2 : p3 : rest) =
hanoiR k (p1 : p3 : p2 : rest) ++
hanoiR (n - k) (p1 : p2 : rest) ++
hanoiR k (p3 : p2 : p1 : rest)
where k
| null rest = n - 1
| otherwise = n `quot` 2
因此请在GHCi中加载并输入
hanoiR 4 [1, 2, 3, 4]
即。用4个圆盘和4个钉子运行河内塔楼。你可以随意命名4个钉子,例如。
hanoiR 4 ['a', 'b', 'c', 'd']
输出:
[(1,2),(1,3),(2,3),(1,4),(1,2),(4,2),(3,1),(3,2),(1,2)]
即。将顶部磁盘从挂钉1移动到挂钉2,然后将顶部磁盘从挂钉1移动到挂钉3等。
我对Haskell很陌生,所以我必须承认,我为此感到自豪。但我可能有愚蠢的错误,所以欢迎反馈。
从代码中可以看出,k的启发式只是floor(n / 2)。我没有试过优化k,虽然n / 2似乎是一个很好的猜测。
我已经验证了4个磁盘和4个挂钩的答案的正确性。对于我来说,在没有编写模拟器的情况下进行更多验证已经太晚了。 (@ _ @)以下是一些结果:
ghci> hanoiR 6 [1, 2, 3, 4, 5]
[(1,2),(1,4),(1,3),(4,3),(2,3),(1,4),(1,5),(1,2),
(5,2),(4,2),(3,1),(3,4),(3,2),(4,2),(1,2)]
ghci> hanoiR 6 [1, 2, 3, 4]
[(1,2),(1,4),(1,3),(4,3),(2,3),(1,2),(1,4),(2,4),(1,2),
(4,1),(4,2),(1,2),(3,1),(3,4),(3,2),(4,2),(1,2)]
ghci> hanoiR 8 [1, 2, 3, 4, 5]
[(1,3),(1,2),(3,2),(1,4),(1,3),(4,3),(2,1),(2,3),(1,3),(1,2),
(1,4),(2,4),(1,5),(1,2),(5,2),(4,1),(4,2),(1,2),
(3,2),(3,1),(2,1),(3,4),(3,2),(4,2),(1,3),(1,2),(3,2)]
这是否澄清了算法?
真的必不可少的是
hanoiR k (p1 : (p3 : (p2 : rest))) ++ -- step 1; corresponds to T(k,r)
hanoiR (n-k) (p1 : (p2 : rest)) ++ -- step 2; corresponds to T(n-k, r-1)
hanoiR k (p3 : (p2 : (p1 : rest))) -- step 3; corresponds to T(k,r)
其中我们连接Frame-Stewart算法的步骤1,2和3的移动序列。为了确定移动,我们按如下方式注释F-S的步骤:
这有意义吗?
答案 1 :(得分:2)
要解决河内之塔,您需要做的就是:
Frame Stewart算法并不那么复杂。基本上,您必须将一定数量的磁盘(例如,其中一半)移动到一些挂钩:将这些磁盘视为自己的独立磁盘。很容易为1或2个磁盘定义解决方案,并且将上半部分移动到目的地,将后半部分移动到最终需要的位置。
如果你想让它更易于编写(唯一的特殊情况变为1),你可以不断对其进行分段,但是没有大量的钉子,它将无法工作。
此外,如果k >= 3
,您可以通过简单地忽略其余的钉子来解决它,就像河内的3个钉塔一样,尽管这不是最佳的。
答案 2 :(得分:2)
欣泽,拉尔夫。 功能珍珠:La Tour D'Hanoi ,http://www.comlab.ox.ac.uk/ralf.hinze/publications/ICFP09.pdf
这颗珍珠旨在展示 全麦和投射的思想 使用河内塔编程 拼图作为一个运行的例子。该 拼图有自己的美,我们 希望一路暴露。
答案 3 :(得分:1)
我想贡献我认为是 Frame-Stewart 算法中最优的 k
。对于无法解决 Hanoi Towers 的情况,没有定义算法本身。
frameStewart :: Integer -> Integer -> Integer
frameStewart 1 _ = 1
frameStewart n 3 = (2^n) - 1
frameStewart n r
| n < r = 2*n - 1
| otherwise = frameStewart k r + frameStewart (n-k) (r-1) + frameStewart k r
where
k = solve (2*n `div` r)
where
solve a
| a * (2*n - a) <= (n^2 - 2*a) = a
| otherwise = solve (a-1)
wikipedia page表示n
个圆盘减去2*n + 1
的平方根,在特殊括号内求最接近的整数,+ 1
等于k
.我发现通过考虑 n = 4
和 k = 2
来摆脱这些括号很有用,并操纵公式直到我得到代码中存在的不等式。仅此一项就解决了 4 个钉子的问题。
最后一步是确定用哪个值来解决该不等式,并认为它是 2*n
除以挂钩数 r
。我认为它的结果与 Ben Houston 研究中的 5 和 6 个挂钩的表格相匹配,这些表格出现在 here。
这不会给你一个移动列表,但是,只有最小数量。
答案 4 :(得分:0)
1883年,法国数学家爱德华·卢卡斯(Edouard Lucas)以笔名N. Lucas de Siam向河流世界发布了“河内之塔”。 &#34;传奇&#34;伴随着游戏的是,在贝纳雷斯,在皇帝佛嗨统治期间,有一个印第安寺庙,其圆顶标志着世界的中心(Kashi Vishwanath)。在圆顶内,(婆罗门)牧师在3个钻石针尖(破旧的柱子)之间移动金色圆盘,高度为肘高,与蜜蜂的身体一样厚。在创造时,上帝(梵天)在一根针上放置了64个金盘。 (光盘根据Brahma的不变法则移动,将它们从一个钉子转移到另一个钉子)据说,当他们完成任务时,宇宙就会结束。图例在不同的网站上有所不同,但一般都是一致的。 &#39;法律&#39;由Brahma设定如下: 1)一次只能移动1张光盘 2)没有光盘可以放在较小的光盘上 3)只能移除顶部光盘,然后将其放置在另一个挂钉及其光盘的顶部 当整堆光盘移动到另一个挂钉时,游戏结束。很快发现3 peg溶液存在,但没有解决4 + peg溶液。 1939年,美国数学月刊举办了一场竞赛,以解决和制作光盘问题。两年后,J. S. Frame和B. M. Stewart发表了两个独立的(但后来证明相同的)算法。两者都尚未被证实是正确的,尽管它们通常被认为是正确的。在适当的解决方案上还没有进一步的进展。 *****这仅适用于3个挂钩问题***** n个磁盘塔的最小移动次数很快显示为2n-1,简单的递归解决方案如下:标记三个桩的起点,目标和温度。通过temp peg将n pegs从起始栓钉移动到目标钉: 对于n> 1, (i)递归地将顶部n-1个磁盘从开始移动到临时目标。 (ii)将第n个磁盘从开始移动到目标。 (iii)通过start递归地将n-1个磁盘从temp移动到目标。 此解决方案需要2n-1个动作: (1)如果n = 1,则f(n)= 1 = 2n-1 (2)如果n> 1,f(n)= 2 *(2n-1-1)+ 1 = 2n-2 + 1 = 2n-1 解决它的简单方法...... 1,3,7,15,31是前几张光盘的解决方案。递归地类似于nk = 2nk-1 + 1。从那里我们可以诱导n = 2n-1。通过归纳证明我留给你。 *****基本的Frame / Stewart算法***** 对于m peg和n个圆盘,选择1使得0≤l<1。 n(而l是一个整数,可以最大限度地减少以下设置中的步骤)...... •将顶部l盘从起始桩移动到中间挂钉;这可以在Hk(l)移动中完成(因为底部n-1个盘根本不干扰移动)。 •使用Hk-1(n-l)移动将底部n-l个磁盘从起始栓钉移动到目标栓钉。 (由于一个钉子被一个较小的圆盘塔占用,因此在这个阶段不能使用它。) •将原始l个磁盘从中间挂钩移动到Hk(l)移动中的目标挂钩。 所以基本上它是Hk(n)= 2Hk(l)+ Hk-1(n-1)-----&gt; l最小化 *****非常简单!!不!***** 验证它是否适用于我们的3 peg解决方案应该很容易。 使用k = 2;我们设置H2(0)= 0,H2(1)= 1,H2(n> 1)=∞。 对于k = 3,我们可以设置l = n-1。 (它导致我们的H2(n-1)变得有限)这将允许我们写入H3(n)= 2H3(n-1)+ H2(1),其等于2n-1。这是一个文字游戏,但它的工作原理。 *****略有不同的描述有助于澄清***** Frame-Stewart算法给出了四个(甚至更多)桩钉的最佳解决方案,如下所述: 将H(n,m)定义为使用m pegs传输n个磁盘所需的最小移动次数 该算法可以递归地描述: 1.对于一些l,1
`Option VBASupport 1
Option Explicit
Dim n as double
dim m as double
dim l as double
dim rx as double
dim rxtra as double
dim r as double
dim x as double
dim s1 as double
dim s2 as double
dim i as integer
dim a ()
dim b ()
dim c ()
dim d ()
dim aa as double
dim bb as double
dim cc as double
dim dd as double
dim total as double
Sub Hanoi
on error goto errorhandler
m=inputbox ("m# pegs=??")
n=inputbox ("n# discs=??")
x=-1
l=m-1
rx=1
s1=0
s2=0
aa=0
while n>rx
x=x+1
r=(l+x)/(x+1)
rx=r*rx
wend
rx=1
for i=0 to x-1
r=(l+i)/(i+1)
rx=r*rx
redim a (-1 to x)
redim b (-1 to x)
redim c (-1 to x)
redim d (-1 to x)
a(i)=rx
b(i)=i
bb=b(i)
c(i)=rx-aa
aa=a(i)
cc=c(i)
d(i)=cc*2^bb
dd=d(i)
s1=s1+dd
next
rxtra=n-aa
s2=rxtra*2^(bb+1)
total = 2*(s1+s2)-1
msgbox total
exit sub
errorhandler: msgbox "dang it!!"
'1, 3, 5, 9, 13, 17, 25, 33 first 8 answers for 4 peg
'16=161,25=577,32=1281,64=18433
End Sub`
披露:这些来源用于确认答案和问题的一些历史。由于多个网站用于验证,所以很难给出确切的信用,因此它们是历史上许多部分的所有来源。
答案 5 :(得分:-1)
Facebook k-peg N disc of hanoi可以通过BFS算法解决。如需解决方案,请访问http://www.woolor.com/InterviewMitra/41/facebook-k-peg-of-tower-of-hanoi-solution
答案 6 :(得分:-1)