Algorithm 河内塔楼与K桩
这个问题是递归的经典问题。给您3个销钉,其中一个销钉上有磁盘,您必须按照给定的规则将所有磁盘从一个销钉移动到另一个销钉。您还必须以最少的移动次数完成此操作 下面是一个解决问题的递归算法:Algorithm 河内塔楼与K桩,algorithm,haskell,f#,recursion,functional-programming,Algorithm,Haskell,F#,Recursion,Functional Programming,这个问题是递归的经典问题。给您3个销钉,其中一个销钉上有磁盘,您必须按照给定的规则将所有磁盘从一个销钉移动到另一个销钉。您还必须以最少的移动次数完成此操作 下面是一个解决问题的递归算法: void Hanoi3(int nDisks, char source, char intermed, char dest) { if( nDisks > 0 ) { Hanoi3(nDisks - 1, source, dest, intermed); c
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;
}
我知道Frame Stewart算法,它很可能是最优的,但尚未得到验证,它提供了移动次数。然而,我感兴趣的是一个严格的递归解决方案,它遵循3和4个桩的递归解决方案的模式,这意味着它打印实际的移动
至少对我来说,维基百科上提供的Frame Stewart算法的伪代码相当抽象,我还没有成功地将其翻译成打印动作的代码。我会接受它的参考实现(对于randomk
),或者更详细的伪代码
我试图想出某种算法来相应地排列标签数组,但没能成功。如有任何建议,我们将不胜感激
更新:
这在函数式语言中似乎更容易解决。
以下是基于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]
要解决Hanoi塔,您只需执行以下操作:
Frame Stewart算法并没有那么复杂。本质上,您必须将一定数量的磁盘(例如,一半)移动到某个固定位置:将这些磁盘视为自己的独立塔。为1或2个磁盘定义解决方案很容易,如果将前半部分移动到其目标位置,则将后半部分移动到其需要结束的位置
如果你想让它更容易写(唯一的特例变成1),你可以不断地将它分段,但是如果没有大量的PEG,它就不会工作
此外,如果
k>=3
,您只需忽略其余的木桩,就可以像解决河内的3个木桩塔一样解决问题,尽管这不是最佳解决方案。要解决河内的木桩塔问题,您只需:
Frame Stewart算法并没有那么复杂。本质上,您必须将一定数量的磁盘(例如,一半)移动到某个固定位置:将这些磁盘视为自己的独立塔。为1或2个磁盘定义解决方案很容易,如果将前半部分移动到其目标位置,则将后半部分移动到其需要结束的位置
如果你想让它更容易写(唯一的特例变成1),你可以不断地将它分段,但是如果没有大量的PEG,它就不会工作
此外,如果
k>=3
,您可以完全像河内的3个peg塔一样通过简单地忽略其余的peg来解决它,尽管这不是最优的。这里是Haskell中的一个实现(更新:在r=3时通过使k=n-1来处理3-peg情况):
——用于n个磁盘和r销钉的河内[p1,p2,…,pr]
汉诺尔:Int->[a]->[(a,a)]
--零磁盘:无需移动。
汉诺威0=[]
--一个磁盘:需要一个移动和两个销钉。
Hanoir1(p1:p2:rest)=[(p1,p2)]--仅用于智能Aleck?
{-
--n个磁盘和3个销钉——不需要;由下面的(null rest)覆盖。
汉诺尔n[p1,p2,p3]=
汉诺尔(n-1)[p1,p3,p2]++
[(p1,p2)]++
汉诺威(n-1)[p3,p2,p1]
-}
--n个磁盘和r>3个销钉:使用Frame Stewart算法
汉诺尔n(p1:p2:p3:rest)=
汉诺尔k(p1:p3:p2:rest)++
汉诺尔(北-北)(p1:p2:rest)++
汉诺尔k(p3:p2:p1:rest)
k在哪里
|空rest=n-1
|否则=n`quot`2
因此,将其加载并输入
hanoir4[1,2,3,4]
也就是说,用4个圆盘和4个木桩运行河内的塔楼。你可以随意给这4个钉子命名
hanoir4['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,以此类推
我是哈斯克尔的新手,所以我必须承认我很自豪这能奏效。但我可能有愚蠢的错误,所以欢迎反馈
正如您从代码中所看到的,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)]
这是否澄清了算法
真正重要的是
hanoirk(p1:(p3:(p2:rest))+--步骤1;对应于T(k,r)
汉诺尔(n-k)(p1:(p2:rest))+--步骤2;对应于T(n-k,r-1)
汉诺尔k(p3:(p2:(p1:休息))--步骤3;对应于T(k,r)
其中,我们将帧Stewart算法的步骤1、2和3的移动序列连接起来。为了确定移动,我们将F-S的步骤注释如下:
- 传统上,当调用hanoi时,目标被定义为(在不丧失通用性的情况下)将磁盘从第一个销钉转移到第二个销钉,使用所有剩余销钉进行临时存储。在递归时,我们使用此约定来定义被分割和征服的子问题的源、目标和允许的存储
- 因此,源peg是p1,目标peg是p2。所有剩余的木桩都可以作为临时存储,以解决河内的顶级问题
- 步骤1,“对于某些k,1这里是Haskell中的一个实现(更新:通过在r=3时使k=n-1来处理3-peg情况):
——用于n个磁盘和r销钉的河内[p1,p2,…,pr] 汉诺尔:Int->[a]->[(a,a)] --零
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)
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)]
`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`
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)