ハノイの塔の解法
十進BASIC_パズル&クイズ へ戻る
大学生のための数学 へ戻る
2013.06.29


  大きさの異なる相似形をした皿が n枚 あります。 皿の直径は等比数列になっています。 それらは全て、 地点1 に重ね置かれています。 上に行くほど小さな皿になるようにして。 皿を置くことのできる地点には 地点1 の他に 地点2 と 地点3 があります。 そこで、 他の皿に触れることなく1枚ずつ皿を移動させて、 同じ地点に置かれた皿は必ず上に行くほど小さくなるように重ねていき、 地点1 に重ね置かれていた皿をすべて 地点2 に移動させるには、 最小で何回皿を移動させなければならないでしょうか。

  例えば2皿の場合は、 上の皿を 地点3 に移動させ、 次に下の皿を 地点2 に移動させ、 最後に上の皿を 地点2 に移動させるという、 最小3回になります。 3皿の場合は、 一番小さな皿を 地点2 に移動させ、 次に真ん中の皿を 地点3 に移動させ、 その次に一番小さな皿を 地点3 に移動させ、 その次に一番大きな皿を 地点2 に移動させ、 その次に一番小さな皿を 地点1 に移動させ、 その次に真ん中の皿を 地点2 に移動させ、 最後に一番小さな皿を 地点2 に移動させるという、 最小7回になります。 一般的な答えは なのですが、 今日お話ししたいことは、 この答えの導き方ではなく、 十進BASIC のプログラムがこんなにも短いものであるのかということです。 それは再帰関数を利用したプログラムです。 しかし、 プログラムを見ただけではそのアルゴリズムがなかなか理解できません。 そこで、 今日はそれを解説したいと思うのです。

  三重大教育学部の奥村晴彦教授は、 著書 「 C言語最新アルゴリズム辞典 」 に記載されているソースコードをネット上で公開されていますが、 その一部を 山中和義 / 電脳遊戯団 さんが十進BASIC に翻訳されてネット上に公開されています。 そこに 「 ハノイの塔 」 を解くプログラムがあります。 それを掲載させていただきます。

   10 SUB movedisk(n,a,b)
   20   IF n>1 THEN CALL movedisk(n-1,a,6-a-b)
   30   PRINT " ";n;"番目に小さな皿を 地点";a;"から 地点";b;"に移す。"
   40   IF n>1 THEN CALL movedisk(n-1,6-a-b,b)
   50 END SUB

   60 INPUT PROMPT "皿の枚数は? ":n
   70 PRINT n;"枚の皿を 地点1 から 地点2 に移す方法は、次の";2^n-1;"手です。"
   80 CALL movedisk(n,1,2)
   90 END

  60 〜 90 がプログラムの本幹です。 10 〜 50 が再帰関数です。 関数の中で自分自身を呼び出すのが再帰関数です。 このプログラムを実行して、 皿の枚数に4を指定すると、 次のように出力されます。 ただし、 右側の番号は出ませんし、 行のズレもありません。

    1 番目に小さな皿を 地点 1 から 地点 3 に移す。  (1)
       2 番目に小さな皿を 地点 1 から 地点 2 に移す。  (2)
    1 番目に小さな皿を 地点 3 から 地点 2 に移す。  (3)
          3 番目に小さな皿を 地点 1 から 地点 3 に移す。  (4)
    1 番目に小さな皿を 地点 2 から 地点 1 に移す。  (5)
       2 番目に小さな皿を 地点 2 から 地点 3 に移す。  (6)
    1 番目に小さな皿を 地点 1 から 地点 3 に移す。  (7)
             4 番目に小さな皿を 地点 1 から 地点 2 に移す。  (8)
    1 番目に小さな皿を 地点 3 から 地点 2 に移す。  (9)
       2 番目に小さな皿を 地点 3 から 地点 1 に移す。  (10)
    1 番目に小さな皿を 地点 2 から 地点 1 に移す。  (11)
          3 番目に小さな皿を 地点 3 から 地点 2 に移す。  (12)
    1 番目に小さな皿を 地点 1 から 地点 3 に移す。  (13)
       2 番目に小さな皿を 地点 1 から 地点 2 に移す。  (14)
    1 番目に小さな皿を 地点 3 から 地点 2 に移す。  (15)


  再帰関数は、 フルクタルと同じで、 内部に入っても先ほどの次元と同じ構造が繰り返されます。 では、 皿4枚の例で、 この再帰関数の内容を見ていくことにしましょう。
  movedisk(n,a,b) は、 地点 a にある n枚 に重ねられた皿たちを 地点 b に移すという操作をします。 最初はn以外の数文字には捕らわれないようにしてください。 この再帰関数は2回も自分自身を呼び出していますので、 気管が細くなるにつれて2つずつに枝分かれしているような構造になっています。 言うならば、 この再帰関数は再帰分岐関数なのです。 再帰関数の内容を理解するためには、 それを内側から眺めるのではなく外側から眺めることが大切です。 つまり、 枝分かれしていく大本から眺めることが大切です。 まず、 4枚の皿を、 最大の皿 と 最大の皿以外の皿の塊 の2つの塊として見てください。 再帰関数を外側から眺めたときに、 30 は最大の皿 ( 4番目に小さな皿 ) を 地点1 から 地点2 に移すことを意味します(8)。 ということは、 20 は最大の皿以外の皿の塊をばらしながら最終的に 地点1 から 地点3 にすべて移しているということです(1〜7)。 最大の皿が 地点1 から 地点2 に移動する直前には、 地点3 において最大の皿以外の皿の塊ができています。 40 は最大の皿以外の皿の塊をばらしながら最終的に 地点3 から 地点2 にすべて移しているということになります(9〜15)。 こうして、 目的が達成されると同時に、 再帰関数の皮が1皮むけてその中が少し見えてきます。 1皮むけた再帰関数は、 3番目に小さな皿までの3枚の皿を取り扱う次元になっています。 そしてそれは、 最大の皿が 地点1 から 地点2 に移動する前と後との2層になっています。
  そこで、 まず、 最大の皿が 地点1 から 地点2 に移動する前の1皮むけた再帰関数の前層から見ていくことにしましょう。 1皮むけた前層の再帰関数を外側から眺めたときに、 3番目に小さな皿 と それより小さな皿たちの塊 の2つの塊として見てください。 30 は3番目に小さな皿を 地点1 から 地点3 に移すことを意味します(4)。 とういことは、 20 は3番目に小さな皿以外の皿の塊をばらしながら最終的に 地点1 から 地点2 にすべて移しているということであり(1〜3)、 40 は3番目に小さな皿以外の皿の塊をばらしながら最終的に 地点2 から 地点3 にすべて移している(5〜7)ということになります。 こうして、 1皮むけた前層の再帰関数の皮の2皮目がむけました。 すると、 2番目に小さな皿 と 最小の皿 の2枚だけが見えてきます。 2皮むけた前層の再帰関数を外側から眺めたときに、 30 は2番目に小さな皿を 地点1 から 地点2 に移すことを意味します(2)。 とういことは、 20 は最小の皿を 地点1 から 地点3 に移すこと(1)を意味するということであり、 40 は最小の皿を 地点3 から 地点2 に移すこと(3)を意味するということになります。 こうして、 前層の再帰関数の皮がすべてむけました。
  次は、 最大の皿が 地点1 から 地点2 に移動した後の1皮むけた再帰関数の後層を見ていきましょう。 1皮むけた後層の再帰関数を外側から眺めたときに、 3番目に小さな皿 と それより小さな皿たちの塊 の2つの塊として見てください。 30 は3番目に小さな皿を 地点3 から 地点2 に移すことを意味します(12)。 とういことは、 20 は3番目に小さな皿以外の皿の塊をばらしながら結局は 地点3 から 地点1 に移している(9〜11)ということであり、 40 は3番目に小さな皿以外の皿の塊をばらしながら結局は 地点1 から 地点2 に移している(13〜15)ということになります。 こうして、 1皮むけた後層の再帰関数の皮の2皮目がむけました。 すると、 2番目に小さな皿 と 最小の皿 の2枚だけが見えてきます。 2皮むけた後層の再帰関数を外側から眺めたときに、 30 は2番目に小さな皿を 地点1 から 地点2 に移すことを意味します。 とういことは、 20 は最小の皿を 地点1 から 地点3 に移すこと(13)を意味するということであり、 40 は最小の皿を 地点3 から 地点2 に移すこと(14)を意味するということになります。 こうして、 後半の再帰関数の皮が3皮ともむけ、 すべてが終了しました。

  今は皿が4枚の場合でしたが、 皿の枚数が多いときは、 この再帰関数を外側から眺めながら1つずつ皮をむいて次元を変更させていくたびに層が2乗ずつ厚くなっていくので、 相当な時間を要します。 また、 私たちがこの再帰関数の内容を観察した順番は、 ノートの左から右に向かって2つずつ分岐していくイメージであるのに対して、 このプログラムの結果の打ち出し方は、 その逆で、 ノートの右から左に向かって2つずつ分岐していくイメージになっています。

  では、 movedisk(n,a,b) の中の数がどのように変化していくのかを見ていきましょう。 4枚の皿で考えます。 もちろん外側から内側へと向かって見ていきます。
   前層の再帰関数は movedisk(n,a,b)  movedisk(n-1,a,6-a-b) と変化します。
   後層の再帰関数は movedisk(n,a,b)  movedisk(n-1,6-a-b,b) と変化します。
   前層の再帰関数は、 移しかえる前の地点は一定で、 移し換える地点が次元が変化する
  たびに交互に変化します。
   後層の再帰関数は、 移し換える地点は一定で、 移し換える前の地点が次元が変化する
  たびに交互に変化します。

                       (1,1,3)  ・・・・(1)
               (2,1,2)
                       (1,3,2)
  ・・・・(3)
        (3,1,3)
                       (1,2,1)
  ・・・・(5)
               (2,2,3)
                       (1,1,3)
  ・・・・(7)
  (4,1,2)
                       (1,3,2)
  ・・・・(9)
               (2,3,1)
                       (1,2,1)
  ・・・・(11)
        (3,3,2)
                       (1,1,3)
  ・・・・(13)
               (2,1,2)
                       (1,3,2)
  ・・・・(15)


  < コピペ用の小窓 >