予選問題5 解答と解説


正解
  (1)
     (i) B  (ii) 1  (iii) A
  (2)
     Pascal        move(n-1,B,A,C) あるいは move(n-1,B,A,C);
     C             move(n-1,b,a,c)
     Quick BASIC   CALL move(n-1,B$,A$,C$)

 これは「再帰」を説明するための格好の例題として 余りにも有名なものであるから、すでに知っている者も多いであろうことは 承知の上で、再帰については是非とも理解していて欲しいという意味を込めて 出題したものである。考え方はこうである:いま、場所A,B,Cの状態が

             _         __
           _|_|_       ↑
         _|_____|_   |n枚
       _|_________|_  |
    __|_____________|__↓______________________________________________
             A                   B                       C
のとき、これを
                                                         _         __
                                                       _|_|_       ↑
                                                     _|_____|_   |n枚
                                                   _|_________|_  |
    ______________________________________________|_____________|__↓___  
             A                   B                       C
に変える(これを行う手続き(関数)がmove(n,A,B,C)である)には、Aの最も下の1枚 を無視すると(それは最も大きい円盤なのでどの円盤もその上に重ねることが可能 なので、あたかも存在しないかのごとく扱うことができる)、まず、 Aにあるn-1枚をCを作業場所に使いながらBに移し(すなわち、move(n-1,A,C,B)を 実行し)
                                 _       __
                               _|_|_     ↑
       _____________         _|_____|_  |n-1枚
    __|_____________|_______|_________|__↓_____________________________
             A                   B                       C
とし、次いで、Aにある1枚をCに移し(writeで回数と円盤番号を出力し)
                                 _       __
                               _|_|_     ↑
                             _|_____|_  |n-1枚    _____________
    ________________________|_________|__↓________|_____________|______
             A                   B                        C
としてから、Cにある(最大円盤である)1枚を無視して、Aを作業場所に使いながら Bにあるn-1枚をCへ移せば(すなわち、move(n-1,B,A,C)を実行すれば)求める最終 状態が得られる。従って、(2)の答は自ずから分かるであろう。

 (1)の答を得るためには再帰的プログラムの実行過程を理解出来なければならない。 そのためには、次のように実行過程を表す「木」を書いてみるのがよい。 move(n,A,B,C)はmove(n-1,A,C,B)を実行する、「countを1増やし writeで回数とどの円盤をどこからどこへ移動するかを出力する」、 move(n-1,B,A,C)を実行する、をこの順序で行うから、このことを 図1のように書くことにすれば、n=3のときの実行過程は 図2のような木で表される (move(0,×,×,×)の部分は・で示した)。 この木を、「最も左の枝を優先的に選びながら下に向かって降りて行き、 降りきったら最も近いところまで後戻りして、まだたどっていない枝を (左の枝の方から優先的に)同様にたどる(→再帰)ことを繰り返す」 という方法でたどる(このような木のたどり方を
底優先探索法 (depth-first search)と呼ぶ)と、 たどりの過程で出会ったwrite() (~~~~~~~部)が出会った順に 実行されるものである。上記の実行過程木をこの方法でたどって5番目に出会う write()は、(5,B,1,A)である。

                       (n,A,B,C)
                         /|\
                       /  |  \
                     /    |    \
                   /      |      \
         (n-1,A,C,B) (count,A,n,C) (n-1,B,A,C)
                     ~~~~~~~~~~~~~

図1(再帰呼び出し部)

                                   (3,A,B,C)
                                     /|\
                                  /   |   \
                               /      |      \
                            /         |         \
                       (2,A,C,B)   (4,A,3,C)    (2,B,A,C)
                                   ~~~~~~~~~
                                     /|\
                                  /   |   \
                               /      |      \
                            /         |         \
                       (2,A,C,B)   (4,A,3,C)    (2,B,A,C)
                                   ~~~~~~~~~
                     /|\                          /|\     
                  /   |  \                      /  |  \
               /      |    \                  /    |    \
            /         |      \              /      |      \
      (1,A,B,C)   (2,A,2,B)  (1,C,A,B)  (1,B,C,A) (6,B,2,C) (1,A,B,C)
                  ~~~~~~~~                        ~~~~~~~~~
       /|\                     /|\        /|\                   /|\
      / | \                   / | \      / | \                 / | \
     /  |  \                 /  |  \    /  |  \               /  |  \
    /   |   \               /   |   \  /   |   \             /   |   \                      
    (1,A,1,C)               (3,C,1,B)  (5,B,1,A)              (7,A,1,C)
    ~~~~~~~~                ~~~~~~~~~  ~~~~~~~~~              ~~~~~~~~~

図2(move(3,A,B,C)の計算過程を表す木)