Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/haskell/8.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Algorithm 河内塔楼与K桩_Algorithm_Haskell_F#_Recursion_Functional Programming - Fatal编程技术网

Algorithm 河内塔楼与K桩

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

这个问题是递归的经典问题。给您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;
}
我知道Frame Stewart算法,它很可能是最优的,但尚未得到验证,它提供了移动次数。然而,我感兴趣的是一个严格的递归解决方案,它遵循3和4个桩的递归解决方案的模式,这意味着它打印实际的移动

至少对我来说,维基百科上提供的Frame Stewart算法的伪代码相当抽象,我还没有成功地将其翻译成打印动作的代码。我会接受它的参考实现(对于random
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]

要解决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)