【发布时间】: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 成员访问
标签: algorithm haskell f# recursion functional-programming