河内塔与 K 钉
Posted
技术标签:
【中文标题】河内塔与 K 钉【英文标题】:Towers of Hanoi with K pegs 【发布时间】:2011-04-06 03:35:29 【问题描述】: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
pegs?递归函数会收到一个char[]
,它会保存每个堆栈的标签,所以函数看起来像这样:
void HanoiK(int nDisks, int kStacks, char labels[]) ...
我知道 Frame-Stewart 算法,它很可能是最优的,但尚未得到证实,它可以为您提供 移动次数。但是,我对遵循 3 和 4 钉的递归解决方案模式的严格递归解决方案感兴趣,这意味着它会打印实际移动。
至少对我来说,Wikipedia 上提供的 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]
【问题讨论】:
如果您已经了解 Frame-Stewart 算法,我看不出还有什么好说的。 Frame-Stewart 算法是一种递归解法,直接将解法推广到 3 层,并给出了数量和实际走法。请参阅en.wikipedia.org/wiki/Tower_of_Hanoi#Frame-Stewart_algorithm 它有一个参数 k,你可以任意(但次优)取为 1,比如说。究竟是什么不符合您的要求? @ShreevatsaR - 首先是参数k
。有没有办法在不尝试所有可能性的情况下选择它以使算法达到最优?其次,即使我选择 k
为 1 或其他常数,我仍然不确定如何将算法转换为代码。我不确定点1. 2. 3.
。我将如何对算法进行编码以引入某些钉子,以及如何让它打印动作?我的主要问题是它似乎被写成一个计数算法,或者至少我不知道如何实现它所以它会打印动作。
@IVlad,k 将被输入给您,它表示免费挂钩的数量。 K=1 将是递归算法的限制情况,您需要切换到 Hanoi3 方法并传递适当的标签。
@IVlad - 忽略我的评论。我对算法中的 k 感到困惑,因为您使用 K 作为挂钩数。我理解选择k的问题。但是,如果您选择 k 为某个常数(或随机选择),那么您可以为其编写代码。
受版权保护 ...“在无限的可解河内图家族上”portal.acm.org/… .. 仅供 ACM 成员访问
【参考方案1】:
这是 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)]
即将顶部圆盘从 peg 1 移到 peg 2,然后将顶部圆盘从 peg 1 移到 peg 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 的步骤注释如下:
通常,当调用 hanoi 时,目标定义为(不失一般性)将磁盘从第一个 peg 转移到第二个 peg,使用所有剩余的 peg 进行临时存储。我们在递归时使用此约定来定义分而治之的子问题的源、目标和允许的存储。 因此,源 peg 是 p1,目标 peg 是 p2。所有剩余的钉子都可用作临时存储,用于*** hanoi 问题。 第 1 步,“对于一些 k,1 因此“不干扰现在包含前 k 个磁盘的 peg”(步骤 2)意味着使用除 p3 之外的所有 peg 进行递归。 IE。 p1、p2 以及 p3 以外的其他部分。 “将前 k 个磁盘转移到目标 peg”(步骤 3)是指从“其他 peg”(p3)转移到 p2。这有意义吗?
【讨论】:
非常好! Haskell 小提示:冒号运算符已经是右结合的,所以你可以只写hanoiR n (p1:p2:p3:rest)
而不是hanoiR n (p1 : (p2 : (p3 : rest)))
等。
不过,您仍然需要 3-peg 解决方案。 (至少是你写它的方式。)
很有道理,谢谢!我设法在 F# 中实现了它,将用这样的实现来更新我的主要帖子。
@Shree,感谢 Haskell 的提示...今天早上我在想,我可能可以摆脱那些括号。关于 3-peg 解决方案,我删除了它,因为我很确定当我有 hanoiR 1 [p1, p2] = ...
时它是多余的。上面显示的测试运行没有 3-peg 解决方案。但随后应从 n=1 解决方案中删除“仅需要智能 alecks”注释。我会编辑它。如果我仍然需要 3-peg 解决方案,请说明。
@LarsH - 我不确定,但我认为如果您运行 hanoiR 5 [1,2,3]
(运行 3 个堆栈的程序),您需要 3-peg 解决方案。在我单独添加 3-peg 解决方案之前,至少我的 F# 解决方案在这种情况下崩溃了。【参考方案2】:
要解决河内塔,您需要做的就是:
Frame Stewart 算法并没有那么复杂。本质上,您必须将一定数量的磁盘(例如,其中一半)移动到某个挂钩上:将这些磁盘视为它们自己的独立塔。为 1 或 2 个磁盘定义解决方案很容易,其中一个将前半部分移动到其目的地,将后半部分移动到它需要结束的位置。
如果你想让它更容易编写(唯一的特殊情况变成 1),你可以不断地对其进行分段,但如果没有大量的钉子,它就行不通。
此外,如果k >= 3
,您可以完全像河内的 3 个钉塔一样解决它,只需忽略其余钉子,尽管这不是最佳选择。
【讨论】:
一个实现会很好。我认为这比K
堆栈看起来更具挑战性。【参考方案3】:
Hinze,拉尔夫。 功能明珠:La Tour D'Hanoi,http://www.comlab.ox.ac.uk/ralf.hinze/publications/ICFP09.pdf
这颗珍珠旨在展示 全麦和投射的想法 使用河内塔编程 谜题作为一个运行的例子。这 拼图有它自己的美,我们 希望一路曝光。
【讨论】:
【参考方案4】:Facebook k-peg N discs tower of hanoi 可以通过 BFS 算法求解。解决方案请访问http://www.woolor.com/InterviewMitra/41/facebook-k-peg-of-tower-of-hanoi-solution
【讨论】:
只有链接的答案是not a good answer。 那家伙没有足够的评论点,所以回答是他互动的唯一选择。【参考方案5】:1883 年,法国数学家爱德华·卢卡斯 (Edouard Lucas) 以 N. Lucas de Siam 的笔名向西方世界发表了“河内塔”谜题。伴随游戏的“传说”称,在佛喜皇帝统治期间,在贝拿勒斯,有一座带有圆顶的印度寺庙,它标志着世界的中心(喀什维什瓦纳特)。在圆顶内,(婆罗门)祭司在 3 个钻石针尖(磨损的柱子)之间移动金色圆盘,针尖高一肘,厚如蜜蜂的身体。上帝(梵天)在创造时将 64 个金盘放在一根针上。 (圆盘是根据梵天的不变法则移动的,将它们从一个钉子转移到另一个钉子上)据说当它们完成任务时,宇宙将结束。图例在多个站点上有所不同,但通常是一致的。 梵天制定的“法则”是这样的: 1) 一次只能移动 1 个光盘 2) 不能将光盘放在较小的光盘上 3) 只能移除顶部的圆盘,然后将其放置在另一个钉子的顶部和它的圆盘上 当整个盘子被移动到另一个钉子上时,游戏结束。很快发现存在 3 peg 解决方案,但没有解决 4+ peg 解决方案。 1939年,美国数学月刊举办了求解m peg和n圆盘的比赛。两年后,J. S. Frame 和 B. M. Stewart 发表了两个独立的(但后来证明是相等的)算法。两者都尚未被证明是正确的,尽管它们通常被认为是正确的。在适当的解决方案上还没有进一步的进展。 *****这仅适用于 3 个挂钩问题 ***** n 盘塔的最小移动次数很快显示为 2n−1,简单的递归解决方案如下:标记三个钉子 start、goal 和 temp。要通过临时挂钩将 n 个挂钩从起始挂钩移动到目标挂钩: 对于 n > 1, (i) 通过目标递归地将前 n-1 个磁盘从 start 移动到 temp。 (ii) 将第 n 个圆盘从起点移动到目标。 (iii) 通过 start 递归地将 n-1 个磁盘从 temp 移动到 goal。 该解决方案需要 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 是前几个 n 盘的解决方案。递归类似于 nk=2nk-1+1。从那里我们可以得出 n=2n-1。通过归纳证明我留给你。 *****基本的 Frame/Stewart 算法***** 对于 m peg 和 n 个圆盘,选择一个 l 使得 0 ≤ l 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 个钉子转移 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`
披露:这些来源用于确认答案和问题的一些历史记录。由于使用了多个站点进行验证,因此很难准确说明其应归于何处……因此它们是历史许多部分的全部来源。
【讨论】:
【参考方案6】:http://tristan-interview.blogspot.com/2012/02/n-disks-and-k-pegs-extension-problem-of.html
【讨论】:
只有链接的答案是not a good answer。以上是关于河内塔与 K 钉的主要内容,如果未能解决你的问题,请参考以下文章