最終更新日: 2012年 3月15日
3x3のスライドパズル(8パズル)に対する配置チェックのサンプルプログラムの 動作確認のため、3x3の全配置パターンで、ゴール可能、不可能の確認をしました。 このページは、そのプログラムと結果についてのメモです。
オライリー・ジャパンの「アルゴリズム・クイックリファレンス」 P213 に記載されている 幅優先探索プログラムの一部を変更して、ゴール可能な配置と不可能な配置を全て確認します。
ゴール可能な配置を確認する場合、ゴールの配置を初期配置として、そこから1つずつ移動させてゴール可能な全パターンを確認します。

同様に、不可能な配置の場合は、ゴールの配置で1と2を入れ替えた以下の配置を初期配置として、ゴール不可能な全パターンを確認します。

サンプルプログラムの実行結果は以下のようになり、3x3(8パズル)ではゴール可能な全配置が181440通りで、その配置全てで、 GoalCheckerクラスによる判定は、全てゴール可能になりました。 また、同様にゴール不可能な全配置は181440通りで、GoalCheckerクラスによる判定は全てゴール不可能になりました。
Initial: Node Board: 1 2 3 ← 初期配置(ゴール可能) 4 5 6 7 8 0 closed: 181440 ← 調べた重複しないゴール可能な配置の数 Possible: 181440 ← GoalCheckerで判定したときのゴール可能な数 Impossibe: 0 ← GoalCheckerで判定したときのゴール不可能な数 Initial: Node Board: 2 1 3 ← 初期配置(ゴール不可能) 4 5 6 7 8 0 closed: 181440 ← 調べた重複しないゴール不可能な配置の数 Possible: 0 ← GoalCheckerで判定したときのゴール可能な数 Impossibe: 181440 ← GoalCheckerで判定したときのゴール不可能な数
プログラムの実行結果のゴール可能と不可能な配置の数を合計すると 362880(181440 + 181440)通りになります。 これは以下の計算した3x3の全配置数と一致します。同様にサンプルプログラムで、2x3や2x4のケースを求めても計算値と一致しました。

4x4(15パズル)や5x5などでは、配置数が多すぎてプログラムを実行すると Out Of Memory Error が起こり、完了できません。 そのため、手動でゴール可能と不可能な配置を数個試し、それらでは正しく判定することを確認しただけです。
public class SlidelGoalCheck {
public static void main(String[] args) {
SlidelGoalCheck sgc = new SlidelGoalCheck();
sgc.test();
}
private INodeSet impossible = StateStorageFactory.create(OpenStateFactory.TYPE.HASH);
private int countPossible;
private int countImpossible;
private SlideGameManager manager;
/**
* Constructor
*
*/
public SlidelGoalCheck() {
// 3x3以上の場合は、Out of Memory Error になりやすいので注意すること
int maxPx = 3;
int maxPy = 3;
manager = new SlideGameManager(maxPx, maxPy);
}
/**
* ゴール可能な配置、不可能な配置の全てで、GoalCheckerが
* 正しく判定しているかを確認する
*
*/
public void test() {
// ゴール可能な配置全てをチェックする
INode initial = new NodeSlideGame(manager.cloneGoalBoard());
countPossible = 0;
countImpossible = 0;
breadthFirst(initial);
// ゴール不可能な配置全てをチェックする
Board goal = manager.cloneGoalBoard();
goal.setNumber(0, 0, 2);
goal.setNumber(1, 0, 1);
initial = new NodeSlideGame(goal);
countPossible = 0;
countImpossible = 0;
breadthFirst(initial);
}
/**
* オライリー・ジャパンの「アルゴリズム・クイックリファレンス」 P213 に記載されている
* 幅優先探索を一部変更したもの。
*
* @param initial 初期配置
*/
public void breadthFirst(INode initial) {
GoalChecker checker = new GoalChecker();
// 初期状態から始める
INodeSet open = StateStorageFactory.create(OpenStateFactory.TYPE.QUEUE);
open.insert(initial.copy());
// すでに訪問した状態
INodeSet closed = StateStorageFactory.create(OpenStateFactory.TYPE.HASH);
while (! open.isEmpty()) {
INode n = open.remove();
closed.insert(n);
// 次の手はすべて、オープン状態に追加されている。
DoubleLinkedList<IMove> moves = n.validMoves();
for (Iterator<IMove> it = moves.iterator(); it.hasNext();) {
IMove move = it.next();
// 盤面状態の集合を保守するために、コピーの上で手を実行する。
INode successor = n.copy();
move.execute(successor);
// 訪問済みなら、この状態はもう探索しない
if (closed.contains(successor) != null) {
continue;
}
open.insert(successor);
}
}
Iterator i = closed.iterator();
while (i.hasNext()) {
INode n = (INode) i.next();
checkGoal(checker, n);
}
System.out.println("Initial: " + initial
+ "closed: " + closed.size()
+ "\nPossible: " + countPossible
+ "\nImpossibe: " + countImpossible
+ "\n"
);
}
private void checkGoal(GoalChecker checker, INode node) {
Board board = ((NodeSlideGame) node).cloneBoard();
if (checker.isPossibleGoal(board) == false) {
countImpossible ++;
} else {
countPossible ++;
}
}
}
修正. 2012.3.30 メソッド breadthFirst の INodeSet open の定義部分が間違っていましたので修正
// 初期状態から始める
INodeSet open = StateStorageFactory.create(OpenStateFactory.TYPE.STACK);
修正済み
// 初期状態から始める
INodeSet open = StateStorageFactory.create(OpenStateFactory.TYPE.QUEUE);
幅優先では、スタックでなくキューを使用します。