web-dev-qa-db-ja.com

ハノイの塔:再帰アルゴリズム

再帰を理解することはまったく問題ありませんが、ハノイの塔の問題に対する再帰的な解決策に頭を悩ませることはできません。 Wikipedia のコードは次のとおりです。

procedure Hanoi(n: integer; source, dest, by: char);
Begin
    if (n=1) then
        writeln('Move the plate from ', source, ' to ', dest)
    else begin
        Hanoi(n-1, source, by, dest);
        writeln('Move the plate from ', source, ' to ', dest);
        Hanoi(n-1, by, dest, source);
    end;
End;

基本ケースと、単一のディスクを移動できるようになるまで問題を小さな断片に分割する概念を理解しています。ただし、非ベースケースの2つの再帰呼び出しがどのように連携するかはわかりません。おそらく誰かが私を助けることができますか?ありがとう。

63
titaniumdecoy

実際には、 そのコードを取得したセクション にも説明があります:

N個のディスクをペグAからペグCに移動するには:

  1. n-1個のディスクをAからBに移動します。これにより、ペグAでディスク#nのみが残ります。
  2. ディスク#nをAからCに移動します
  3. n-1個のディスクをBからCに移動して、ディスク#nに配置する

最初にnn)にアクセスするには、1ディスクを削除する必要があることは明らかです。そして、最初に完全なタワーを表示する場所とは別のペグに移動する必要があります。

投稿のコードには、ディスクの数に加えて、3つの引数があります:Asourcepeg、adestinationpegおよびatemporary間にディスクを格納できるペグ(サイズn-1のすべてのディスクが適合する)。

再帰は実際には2回発生します。1回はwritelnの前、1回は後です。 writelnの前の1つはn− 1ディスクを一時ペグに移動し、宛先ペグを一時ストレージとして使用します(再帰呼び出しの引数は異なります注文)。その後、残りのディスクは目的のペグに移動し、その後、2番目の再帰は、n− 1タワーを一時ペグから移動することにより、タワー全体の移動を強制しますディスク上の宛先ペグn。

46
Joey

1年前、関数型プログラミングコースを受講し、このイラストをアルゴリズムに使用しました。それが役に立てば幸い!

(0)  _|_         |          |
    __|__        |          |
   ___|___       |          |
  ____|____  ____|____  ____|____

(1.1) |          |          |
    __|__        |          |
   ___|___      _|_         |
  ____|____  ____|____  ____|____ (A -> B)

(1.2) |          |          |
      |          |          |
   ___|___      _|_       __|__
  ____|____  ____|____  ____|____ (A -> C)

(1.3) |          |          |
      |          |         _|_
   ___|___       |        __|__
  ____|____  ____|____  ____|____ (B -> C)



(2.1) |          |          |
      |          |         _|_
      |       ___|___     __|__
  ____|____  ____|____  ____|____ (A -> B)



(3.1) |          |          |
      |          |          |
     _|_      ___|___     __|__
  ____|____  ____|____  ____|____ (C -> A)

(3.2) |          |          |
      |        __|__        |
     _|_      ___|___       |
  ____|____  ____|____  ____|____ (C -> B)

(3.3) |         _|_         |
      |        __|__        |
      |       ___|___       |
  ____|____  ____|____  ____|____ (A -> B)

3つのリングの問題は、2つの2リングの問題(1.xおよび3.x)に分割されました。

31
f0b0s

http://www.cs.cmu.edu/~cburch/survey/recurse/hanoiimpl.html に、再帰的なハノイの実装に関する適切な説明があります。

要約すると、下のプレートをスティックAからスティックBに移動する場合、最初にその上の小さなプレートをすべてAからCに移動する必要があります。2番目の再帰呼び出しは、移動したプレートをCに移動することですベースケースが1つの大きなプレートをAからBに移動した後、Bに戻ります。

14
matthock

私はこれを最初に見たときすぐにではないことに同意しますが、それを理解するのはかなり簡単です。

ベースケース:タワーのサイズは1です。そのため、ソースからデスティネーションに直接移動することができます。

再帰的なケース:タワーのサイズはn> 1です。したがって、サイズn-1の上部タワーを追加のペグ(by)に移動し、サイズ1の下部「タワー」を目的のペグに移動し、上部タワーを移動しますからdestへ。

したがって、単純なケースでは、高さ2のタワーがあります。

 _|_    |     |
__|__   |     |
===== ===== =====

最初のステップ:2-1(= 1)の一番上のタワーを余分なペグ(中央のペグ、たとえば)に移動します。

  |     |     |
__|__  _|_    |
===== ===== =====

次に:下のディスクを目的地に移動します:

  |     |     |
  |    _|_  __|__
===== ===== =====

最後に、(2-1)= 1の一番上の塔を目的地に移動します。

  |     |    _|_
  |     |   __|__
===== ===== =====

考えてみると、タワーが3つ以上あったとしても、タワーを交換するときに再帰を使用するために、空の余分なペグ、またはすべての大きなディスクのペグが常にあります。

13
Sean

ディスクをAからBを介してCに移動するとします。

  1. 小さいディスクをBに移動します。
  2. 別のディスクをCに移動します。
  3. bをCに移動します。
  4. aからBに移動します。
  5. すべてをCからAに移動します。

上記の手順をすべて繰り返すと、ディスクが転送されます。

痛みを感じます!

これは古い記事ですが、本当に理解する必要があるのは、「これに移行する」アプローチではなく、再帰の副作用を使用することです。

私にとってかけがえのない助けは、再帰関数を考えて書くことを教える「The Little Schemer」でした。

ただし、これは、返された結果の結果を次の再帰呼び出しで使用することを読者に教えます。

ハノイの塔では、答えは返された結果そのものではなく、返された結果の観察にあります。

magicは、関数パラメーターの連続的な再配置で発生します。

はい、問題は本当に3つの部分にあります。

  • 小さいペグを予備のペグに移動する
  • 最後のディスクを目的のペグに移動する
  • スペアペグの残りのタワーを目的のペグに移動します。

スキーム内:

(define (th n a b c)
    (if (zero? n) 'done
        (begin
           (th (- n 1) a c b)
           (display (list a c))
           (newline)
           (th (- n 1) b a c))))
(th 5 'source 'spare 'destination)

ただし、問題の解決策は関数パラメーターの表示であり、呼び出しの構造のような二重ツリーを決定的に理解しています。

このソリューションは、proof by inductionおよびwarm glowは、従来の制御構造と格闘したすべてのプログラマーに提供します。

ちなみに、手で問題を解決することは非常に満足です。

  • ディスクの数を数える
  • 偶数の場合、最初のディスクを予備のペグに移動し、次の合法的な移動を行います(一番上のディスクは関係しません)。次に、一番上のディスクを目的のペグに移動し、次の合法的な移動(nittd)を行います。次に、一番上のディスクをソースペグに移動し、次の合法的な移動(nittd)を行います...
  • 奇数の場合、最初のディスクを移動先のペグに移動し、次の合法的な移動を行います(一番上のディスクは関係しません)。次に、上部のディスクを予備のペグに移動し、次の合法的な移動(nittd)を行います。次に、一番上のディスクをソースペグに移動し、次の合法的な移動(nittd)を行います...

常に同じ手で一番上のディスクを持ち、常に同じ方向にその手を動かすことにより、最もよく行われます。

nディスクの最終的な移動数は2^n - 1 the move n disc to destinationはプロセスの途中です。

最後に、explaining同僚、あなたの妻/夫、さらには犬(彼らが聞いていなくても)が問題を啓発に結び付けることができるのは面白いです。

3
potong

ディスクの直径が整数(4,3,2,1)で表されるスタックと考えてください。最初の再帰呼び出しは3回呼び出され、次のようにランタイムスタックがいっぱいになります。

  1. 最初の呼び出し:1. 2番目の呼び出し:2,1。 3番目の呼び出し:3,2,1。

最初の再帰が終了した後、ランタイムスタックの内容は、最大直径から最小直径(最初から最後まで)から中央の極にポップされます。次に、直径4のディスクが宛先に移動されます。

2番目の再帰呼び出しは、最初の呼び出しと同じですが、要素を中間の極から目的地に移動する点が異なります。

2
Ahmad

これらすべての説明を読んだ後、ハノイの塔の再帰的ソリューションを説明するために私の教授が使用した方法を検討することにしました。 nがリングの数を表し、A、B、Cがペグを表すアルゴリズムを再度示します。関数の最初のパラメーターはリングの数、2番目のパラメーターはソースペグ、3番目は宛先ペグ、4番目はスペアペグです。

procedure Hanoi(n, A, B, C);
  if n == 1
    move ring n from peg A to peg B
  else
    Hanoi(n-1, A, C, B);
    move ring n-1 from A to C
    Hanoi(n-1, C, B, A);
end;

私は大学院で、小さく考えることを決して恥じないように教えられました。したがって、n = 5のこのアルゴリズムを見てみましょう。最初に自問する質問は5番目のリングをAからBに移動したい場合、他の4つのリングはどこですか? 5番目のリングペグAを占有し、ペグBに移動したい場合、他の4つのリングはペグCにのみ配置できます。上記のアルゴリズムでは、関数Hanoi(n-1、A、C、B)他の4つのリングすべてをペグCに移動しようとしているため、リング5はAからBに移動できます。このアルゴリズムに従って、n = 4を調べます。リング4がAからCに移動する場合、リング3以下ペグBにのみ配置できます。次に、n = 3の場合、リング3がAからBに移動する場合、リング2と1はどこにありますか?もちろんペグCについて。このパターンを続けると、再帰アルゴリズムが何をしているかを視覚化できます。このアプローチは、最後のディスクを最初に見て最初のディスクを最後に見るという点で、初心者のアプローチとは異なります。

2
MNRC
public static void hanoi(int number, String source, String aux, String dest)
{
    if (number == 1)
    {
        System.out.println(source + " - > "+dest);
    }
    else{
        hanoi(number -1, source, dest, aux);
        hanoi(1, source, aux, dest);
        hanoi(number -1, aux, source, dest);
    }
}
1
Rafael Amsili

ここに説明があります。写真を見て->

enter image description here

Movetower(3,a,b,c)を呼び出すことにより、3つのディスクすべてをタワーAからタワーBに移動します。したがって、順次呼び出しは->

1. Movetower(3,a,b,c)  // No Move needed
2. Movetower(2,a,c,b)  // No move needed
3. Movetower(1,a,b,c)  // Here is the time to move, move disc1 from a to b
4. Movetower(2,a,c,b)  // Returning to this call again, this is the time to move disc2 from a to c
5. Movetower(1,b,c,a)  // Again the time to move, this time disc1 from b to c
6. Movetower(3,a,b,c)  // Returning to this call again, this is the time to move disc3 from a to b
7. Movetower(2,c,b,a)  // Not the time to move
8. Movetower(1,c,a,b)  // Here is the time to move, move disc1 from c to a
9. Movetower(2,c,b,a)  // Returning to this call again, this is the time to move disc2 from c to b
10.Movetower(1,c,a,b)  // Here is the time to move, move disc1 from a to b

それが役に立てば幸い :)

アニメーションの場合: https://www.cs.cmu.edu/~cburch/survey/recurse/hanoiex.html

1
jbsu32

最初の再帰呼び出しは、destを補助パイルとして使用して、ソースから最大のものを除くすべてのピースを移動します。完了すると、最大のものを除くすべてのピースが横になり、最大のものは無料になります。これで、最大のものをdestに移動し、別の再帰呼び出しを使用して、すべてのピースをbyからdestに移動できます。

再帰呼び出しは最大のピースについては何も知りません(つまり無視します)が、再帰呼び出しは小さいピースのみを処理するため、最大のピースに自由に移動したり、移動したりできるため、それは問題ありません。

1
sepp2k

それは簡単です。 AからCに移動するとします

ディスクが1つしかない場合は、移動するだけです。

複数のディスクがある場合は、実行します

  • 一番下のディスクを除くすべてのディスク(n-1ディスク)をAからBに移動する
  • 下のディスクをAからCに移動します
  • n-1ディスクを最初のステップからAからCに移動します

N-1個のディスクを移動する場合、n番目はまったく問題にならないことに注意してください(他のすべてのディスクよりも大きい場合)

N-1ディスクを移動すると、n-1 = 1になるまで同じ問題が繰り返されることに注意してください。

1
Samuel Carrijo

私たちの友人の何人かが示唆したように、私は以前の2つの答えを削除し、ここで統合します。

これにより、明確な理解が得られます。

一般的なアルゴリズムとは...

アルゴリズム:

solve(n,s,i,d) //solve n discs from s to d, s-source i-intermediate d-destination
{
    if(n==0)return;
    solve(n-1,s,d,i); // solve n-1 discs from s to i Note:recursive call, not just move
    move from s to d; // after moving n-1 discs from s to d, a left disc in s is moved to d
    solve(n-1,i,s,d); // we have left n-1 disc in 'i', so bringing it to from i to d (recursive call)
}

これが実例です ここをクリック

1
Vivek MVK

質問に対する答えは、プログラムがどのように知っているかということで、偶数は「src」から「aux」であり、奇数は「src」から「dst」であり、最初の動きはプログラムにあります。 4枚のディスクで拳の動きを分解すると、次のようになります。

hanoi(4, "src", "aux", "dst");
if (disc > 0) {
    hanoi(3, 'src', 'dst', 'aux');
        if (disc > 0) {
            hanoi(2, 'src', 'aux', 'dst');
                if (disc > 0) {
                    hanoi(1, 'src', 'dst', 'aux');
                        if (disc > 0) {
                            hanoi(0, 'src', 'aux', 'dst');
                                END
                        document.writeln("Move disc" + 1 + "from" + Src + "to" + Aux);
                        hanoi(0, 'aux', 'src', 'dst');
                                END
                        }

また、4ディスク(偶数)での最初の移動は、SrcからAuxになります。

1
dspkwa
void TOH(int n, int a, int b){
        /*Assuming a as source stack numbered as 1, b as spare stack numbered as 2 and  c as target stack numbered as 3. So once we know values of a and b, we can determine c as there sum is a constant number (3+2+1=)6.
       */
int c = 6-a-b;
if(n==1){
    cout<<"Move from "<<a<<" to "<<b<<"\n";
}
else{
    // Move n-1 disks from 1st to 2nd stack. As we are not allowed to move more than one disks at a time, we do it by recursion. Breaking the problem into a simpler problem.
    TOH(n-1, a, c);
    // Move the last alone disk from 1st to 3rd stack.
    TOH(1, a, b);
    // Put n-1 disks from 2nd to 3rd stack. As we are not allowed to move more than one disks at a time, we do it by recursion. Breaking the problem into a simpler problem.        
    TOH(n-1, c, b);
}
}
int main() {

TOH(2, 1, 3);
cout<<"FINISHED                        \n";
TOH(3, 1, 3);
cout<<"FINISHED                        \n";
TOH(4, 1, 3);
return 0;
}
1
Aditya Raj

ソースタワー、デスティネーションタワー、ヘルパータワーの3つのタワーがあります。ソースタワーにはすべてのディスクがあり、ターゲットはすべてのディスクをターゲットタワーに移動することです。そうすることで、小さなディスクの上に大きなディスクを置くことはありません。以下の手順で再帰を使用してこの問題を解決できます。

nソースタワーのディスク数

基本ケース:n = 1ソースタワーにディスクが1つしかない場合、それを宛先タワーに移動します。

再帰的なケース:n> 1

  • 上位n-1個のディスクをソースタワーからヘルパータワーに移動します
  • 残りのn番目のディスク(ステップ1の後)を宛先タワーに移動します
  • 現在ヘルパータワーにあるn-1個のディスクを宛先に移動します
    タワー、ソースタワーをヘルパーとして使用。

Javaのソースコード:

private void towersOfHanoi(int n, char source, char destination, char helper) {
    //Base case, If there is only one disk move it direct from source to destination
    if(n==1){
        System.out.println("Move from "+source+" to "+destination);
    }
    else{
        //Step1: Move the top n-1 disks from source to helper
        towersOfHanoi(n-1, source, helper, destination);
        //Step2: Move the nth disk from source to destination
        System.out.println("Move from "+source+" to "+destination);
        /*Step3: Move the n-1 disks(those you moved from source to helper in step1) 
         * from helper to destination, using source(empty after step2) as helper
         */
        towersOfHanoi(n-1, helper, destination, source);
    }
}
1
bpjoshi

Golangで再帰を使用したハノイの塔の問題に対する私のソリューションコードです。 `パッケージメイン

import "fmt"

func main() {
    toi(4, "src", "dest", "swap")
}

func toi(n int, from, to, swap string) {
    if n == 0 {
        return
    }
    if n == 1 {
        fmt.Printf("mov %v %v -> %v\n", n, from, to)
        return
    }
    toi(n-1, from, swap, to)
    fmt.Printf("mov %v %v -> %v\n", n, from, to)
    toi(n-1, swap, to, from)
}`
0
Gaurav Sinha

このpython3の例では、再帰的なソリューションを使用しています。

# Hanoi towers puzzle
# for each n, you have to move n-1 disks off the n disk onto another peg
# then you move the n disk to a free peg
# then you move the n-1 disks on the other peg back onto the n disk

def hanoi(n):
    if n == 1:
        return 1
    else:
        return hanoi(n-1) + 1 + hanoi(n-1)


for i in range(1, 11):
    print(f"n={i}, moves={hanoi(i)}")

出力:

n=1, moves=1
n=2, moves=3
n=3, moves=7
n=4, moves=15
n=5, moves=31
n=6, moves=63
n=7, moves=127
n=8, moves=255
n=9, moves=511
n=10, moves=1023

しかし、もちろん、何回の動きを計算する最も効率的な方法は、答えが常に1 ^ 2 ^ nであることを認識することです。したがって、数学的な解は2 ^ n-1です。

0
Angus Comber

CSの学生として、数学的帰納について聞いたことがあるかもしれません。ハノイの塔の再帰的な解決策も同様に機能します。完全な塔が最終的にそうであるように、BとCで本当に失われないようにすることは、異なる部分だけです。

0
weismat

単純な意味では、3つの定義されたタワーのうち、存在するディスクと同じ順序で別のタワーを埋めることで、手順中に大きなディスクが小さなディスクと重なることはありません。

「A」、「B」、「C」を3つの塔とします。 「A」は最初は「n」枚のディスクを含むタワーになります。 「B」は中間タワーとして使用でき、「C」はターゲットタワーです。

アルゴは次のとおりです。

  1. 「C」を使用してn-1個のディスクをタワー「A」から「B」に移動する
  2. ディスクを「A」から「C」に移動する
  3. 「A」を使用してn-1個のディスクをタワー「B」から「C」に移動する

Javaのコードは次のとおりです。

パブリッククラスTowerOfHanoi {

public void TOH(int n, int A , int B , int C){
    if (n>0){
        TOH(n-1,A,C,B);
        System.out.println("Move a disk from tower "+A +" to tower " + C);
        TOH(n-1,B,A,C);
    }
}

public static void main(String[] args) {
    new TowerOfHanoi().TOH(3, 1, 2, 3);
}   

}

0
mohit verma