箱入り娘


箱入り娘パズル

15パズル等と同じ、スライドパズルである。 空白の部分を利用して配置を変え、目的の配置にする。

上図のような配置からスタートして、娘を下の出口から外に 連れ出すパズルである。娘以外は外に出してはいけない。

最小手数は85手。

まずは計算機に解かせる前に自分で遊んでみると、あまりの難しさに 驚くことだろう。

Javaアプレット

試しに箱入り娘パズルで遊べるJavaアプレットを作成した。Initで初期配置に 戻せる。計算機に解かせるときは、Solveを押し(Stopで中断可能)、 解き終わったらNextを次々と押して答えを見る。

ソースはここ。 なお、解くのに使っているのアルゴリズムは下記の2分探索木を用いる方法である。

解法

この問題を解くには、バックトラック(深さ優先探索)よりも、 幅優先探索が適している。理由は、 が挙げられる。

アルゴリズムは比較的単純である。初期状態から1手で到達できる 全ての局面を調べ、記憶する。次に、今調べた1手で到達できる局面を 元に、2手で到達できる局面を全て調べて記憶する。以下同様に 調べ、目的の状態に到達するか、全ての局面を調べ終わったら終了する。 ただし、ループを防ぐために、新たな手が生成されたら過去の局面を 全て調べ、同一局面が過去に存在したら登録しない。

上記に加えていくつか問題に特有の工夫を加えることによって 実行時間を改善できるが、特に下の工夫は必要であろう。

上記の工夫を入れて作ったプログラムが、 hako.c である。 本プログラムでは、過去の局面を双方向リストで管理している。 理由は、同一局面チェックを新しいものから順に行った方が 速いのではないかということで、同一局面チェックを逆順に 行うためである。

実行時間は、MMX Pentium 166M, FreeBSD 2.2.8で、約122秒であった。

改良案1

上記プログラムの実行時間の大半は、過去の局面の探索に費されている。 そこで、この部分を高速化することを考える。

探索の高速化と言えば、二分探索木やハッシュが考えられるが、ここでは 二分探索木を用いてみた。 こうして作ったプログラムが、 hako2.c である。

これで実行時間は約4.7秒まで短縮した。 登録される局面数は最終的には24,000に達するので、それが10数回の 比較で済んでしまうことの効果は劇的である。

改良案2

この問題のようなスライドパズルには、局面Aから局面Bを生成できるなら 局面Bから局面Aを生成できるという性質がある(逆行可能)。 この性質を用いると、実は局面探索は高々2手前まで行えば十分だという ことが証明できる。すなわち、今n手目からn+1手目を生成しているとすると、 重複チェックはn-1手目、n手目、n+1手目だけ行えば良い。

すなわち、今n手目の局面Aから、n+1手目の局面Bを生成したとする。 このとき、n-2手目以前に局面Bは存在しない。

背理法で証明する。この場合、局面チェックを行っているから n-1手目以前に局面Aは存在しない。ところがn-2手目以前に 局面Bが存在したとすると、そこから局面Aが生成できるのでn-1手目以前に 局面Aが存在することになる。これは矛盾。

これを用いれば、探索数を劇的に減らすことが出来る。 hako.c でコメントアウトされている「/* #define MINUS2 */」を生かせば この方法になる(たった一行追加するだけ)。

実行時間は、約23秒であった。

改良案3

また、やや問題の特殊性に依りすぎではあるが、 という案が考えられる。 これを上記3つの方法に加えるには、 コメントアウトされている「/* #define SYM */」を生かせば よい。

実行時間は、各々、31秒、2.2秒、6.3秒まで短縮した。

まとめ

以上をまとめると、実行時間はMMX Pentium 166M, FreeBSD 2.2.8で、

線形探索二分探索木2手前まで検索
対称性を利用しない122秒4.7秒23秒
対称性を利用する31秒2.2秒6.3秒

となった。


箱入り娘 / kashi@mn.waseda.ac.jp