ホームページへ戻る 開発ノートメニューへ戻る
15パズル自動解答プログラムの作り方
現時点において幅優先探索では4×3(11パズル)の最長手数解を求めるのが限界のようです。4×4(15パズル)になると幅優先探索はもう使えません。そこで反復深化を使います。反復深化は同じ探索を繰り返し実行しますからソルバーとしてはとても遅いものになりがちなので「なんとか工夫して少しでも高速なソルバーを作る」これがここでのテーマとなります。
深さ優先探索に「深さ制限」を設け、その「深さ制限」を徐々に大きくしながら探索を何度も繰り返すのが反復深化(IDA*)という探索アルゴリズムです。反復深化には「最短解が求められる」「局面を保存する場所が要らない」「プログラムが簡単に書ける」といった利点がありますが、同じ探索を何度も行うので「探索時間がかかり過ぎる」それから「堂々巡りする探索の流れを防げない」という欠点があります。ただ、堂々巡りを防げないと言っても次の1手で親局面に戻ってしまう「1手の手戻り」程度なら簡単に防止できます。それだけの工夫でもかなりの時間を短縮出来るようです。
反復深化には常套手段ともいえる枝刈り法があります。たとえば「5パズル」を解いている途中を示した下図で1の駒が最後にたどりつく場所まで2マス離れているので「最低でも2手」は絶対に必要となります。
|
|
1は最低でも2手必要
2は最低でも2手必要
3は最低でも3手必要
4は最低でも1手必要
5は最低でも2手必要
|
|
合計、最低でもあと10手必要
|
同様にして、全部の駒の最低限必要な手数(距離)の合計数は絶対に必要となります。こういうように以後絶対に必要なことが明確な値を下限値 (LowerBound)と言って、
|
現在の深さ+下限値(LowerBound)>今回の深さ制限
|
という条件式を調べ、この式が成立すればこれ以上先に進んでもムダということになります。
参考文献(1)にはManhattanDistance(マンハッタンディスタンス)という概念(単語)が登場します。この詳しい説明は全くありませんが話しの流れから推測すると、先ほど説明した「全部の駒の最低限必要な手数(距離)の合計値」のことだろうと思われます。誰にでも直ぐ思い付く発想ですからまず間違いないと思います。
このMDをLowerBoundに採用して枝刈りすると、乱数で無造作に作られた15パズルの問題程度なら充分に解けてしまうという結果が出ています。尚、MD値は親局面での値に対して「動かした駒だけの変位ぶん」しか変化しないことに着目しましょう。毎回すべての駒の距離を集計するなんでムダなプログラムを書いてはいけません。ところで、MDの最大値は60であることがdeepgreenさんによって得られています。
最初の空きマスの位置によって解の手数が偶数か奇数か決まってしまいます。ボードのマスに白黒の市松模様を付けたとして、1手動かす度に空きマスの位置は市松模様上の白黒を交互に移動するので、空きマスの最終地点が黒で最初の地点も黒なら解の手数は偶数になり、最初の地点が白なら解の手数は奇数になります。したがって、深さ制限の初期値を0か1にした後は増加量を+2として深さ制限を大きくしていくことが出来ます。さらに欲を言えば初期値は問題面でのLowerBound値から開始した方が良いでしょう。
MDとは全く異なる考え方を持ちLowerBoundとして採用できる「転倒距離」という別の概念が存在します。
先ず転倒数を数えます。転倒数の意味が分からない場合は「11パズルの最適解が最長手数となる面の探索」の説明を見てください。さて、駒の上下動によって転倒数には、-3,-1,+1,+3の変化が生じます。このことから「最低限必要な上下動の回数」は、転倒数÷3の計算によって得ることができます。さらにこの計算に余りが生じればその余りを加算することができます。例えば転倒数=7なら+3+3+1で最低3回、転倒数=8なら+3+3+1+1または+3+3+3-1で最低4回の上下動が必要になるのです。
|
最低限必要な上下動回数 = 転倒数 / 3 + 転倒数 % 3
|
次に「最低限必要な左右動」の回数を得るために転倒数を数える方向を縦方向優先にして(駒の数字も変換して)見直せば同様に最低限必要な左右動の回数を求めることができます。そして両者の値を合算したものがID(InvertDistance)です。
例えば参考文献(2)に載っている78手の問題(下図)はMD=58ですがIDを計算すると70になります。さらにこのIDはMDと同様に親局面からの差分のみを考慮して求められるという利点もあります。尚、このIDの最大値は70です。
| 15 | 14 | 13 |
| 12 | 11 | 10 | 9 |
| 8 | 7 | 6 | 5 |
| 4 | 3 | 2 | 1 |
|
(MD=58, ID=70)
LowerBound=70
|
最終的にLowerBound値はMDかIDのどちらか「より厳しいほうの値(大きい値)」を採用することになります。下限値の見積り法はひとつとはかぎりません。「何種類かの下限値の見積り法を試してそのうちより大きい値を採用する」というのは互いの不完全さを補い合うとても理に叶った方法といえるでしょう。
ID(InvertDistance)は上下動と左右動を別々に求めて両者を合算していますが、MD(ManhattanDistance)も実は上下動と左右動の合算によるものであることを再確認しましょう。MDを上下動と左右動に分割して改めて眺めて見ると「MDって随分あまいなぁ」と思えてきます。下の図ではMD=3ですが「たった3手で解ける訳がない!」
| 1 | 2 | 3 |
|
| 5 | 6 | 7 | 8 |
| 9 | 10 | 11 | 12 |
| 13 | 14 | 15 | 4 |
|
|
(MD=3)
|
もう少し質の良いMDの値を得ることは出来ないのだろうか。そういう願望に基づき「上下動だけに着目して最低で何手必要になるか」という探索をしてみます。左右の動きは全く無視しますから駒の数字識別も4種類(ホームポジションが何段目の数字か)だけで考え、各状態は次のようなテーブルで表現することにします。上の問題面はこうなります。
| 1段目に属 する駒の数 | 2段目に属 する駒の数 | 3段目に属 する駒の数 | 4段目に属 する駒の数 | 空きマス位置 |
| 1段目 | 3 | 0 | 0 | 0 | ←この段 |
| 2段目 | 0 | 4 | 0 | 0 |
|
| 3段目 | 0 | 0 | 4 | 0 |
|
| 4段目 | 1 | 0 | 0 | 3 |
|
「空きマスのある段」と隣接する上か下の段から駒をそこに移動してくることができます。上の例では「2段目」にある「2段目に属する駒」を「1段目」に移動する方法しかありません。それが1手という考え方になります。
さて、このような探索を「完成された状態から」の逆解きで幅優先探索を実行し、結果をデータベースとして保存しておきます。データベースといってもデータ量はわずか24964パターンだけです。
このデータテーブルを参照すれば任意の状態からMDより質の良い「最低限必要な上下動の回数」を得ることができます。(もちろん左右動についても同様)これがWD (WalkingDistance)です。
はじめのMD=3の面なら、WDは11という値を得ることができます。尚、WDはMDの完全上位互換の値を叩き出しますのでWDを採用すればMDは全く不要になります。(WDの最大値は70)
MDやIDは親局面との差分のみを計算してその値を得ることが出来ましたが、WDのようにデータテーブルを参照してその値を得ようとした場合、参照に係るコストは意外に大きなものがあります。16個のマスのすべてをスキャンしてテーブルを参照するためのキーを求める必要があります。
しかし、ここで参照キーを計算無しの一発で知ることが出来るうまい方法があります。
参照するレコードの中にはWDの値だけでなく次の手(上下左右)によって次局面となる局面の参照キーをリンクさせておけばよいのです。
WDテーブルの場合は「上か下かにN段目に属する駒を移動する」という8通りの選択に基づいた次局面の参照キーをレコードに書き込んでおくことになります。最もこんな贅沢な双方向リンクリスト構造を形成出来るのはWDのデータテーブルが24964個しかないからです。
IDとWDのうち、より厳しい値(大きい値)をLowerBound値にして枝刈りさせる反復深化のプログラムは、例えばこのようなものです。
-
・ puz15wd.c WDテーブルを作るプログラム
・ puz15sv.c 15パズルを解くソルバー本体
ソルバー本体は[puz15sv.c]です。コンパイルして実行させるには、先にpuz15wd.cを実行して得られるpuz15wd.db (610KBのWDテーブル)を同一フォルダに置いておく必要があります。
さて、このソルバーはどの程度の性能があるのか。そもそもこのプログラムは「15パズルの最長手数は何手?」という究極問題を解く目的のためのパーツとして作ったものです。その目的からすれば、このプログラムはまだまだ遅すぎて使い物になりませんが、通常のソルバープログラムとしてはそれなりの性能になったのではないかと思っています。
注意) ID (InvertDistance)とWD (WalkingDistance)は私のオリジナルです。転用・流用の際は必ずこのページを参考文献として明記してください。
このプログラムには天敵とも言える悪魔のような問題面があります。それが下の問題面です。WDもIDもまるでザルのように手数を取りこぼしてしまいます。これを解決するには参考文献(1)の主題となっているPDB(PatternDataBase)を追加する必要があるでしょう。(そのバージョンは「PDBを使った15パズル自動解答プログラム」をご覧下さい。)
1 5 9 13
2 6 10 14
3 7 11 15
4 8 12 0
(WD=20/20,ID=12/12) LowerBound=40
[72 moves] time=77565sec (約21時間33分)
15 14 13 9 10 13 14 11 13 14
11 15 12 13 7 8 13 7 14 6
8 3 4 13 7 14 6 8 5 10
8 6 3 4 2 5 6 3 4 2
5 6 3 4 15 11 9 8 4 9
8 4 10 3 2 7 14 15 9 10
3 2 7 9 10 7 6 5 9 10
11 12
それにしても「15パズルの最長手数は何手?」というテーマですが、現時点で80手の問題は発見されています。また参考文献(1)には最長手数は90手以下であると書いてあります。つまり80手〜90手の範囲内に獲物は潜んでいます。
サムロイドがこの15パズルを考案したのは1878年のことです。100年以上の魔法が解ける瞬間は近いのでしょうか。(下の追記を見てください)
1997年にスイスの研究チーム(Adrian Brungger, Ambros Marzetta, Komei Fukuda, Jurg Nievergelt)によって「15パズルの最長手数は80手」であることが立証されていました。NEC製CENJU-3という並列コンピュータ(CPU=64個-128個)を使用して230日間かかる探索を3日間で処理したとのことです。
ホームページへ戻る 開発ノートメニューへ戻る