数独
問題を与えられたら、それを解くプログラムを作ってみました。
すべての問題はムリですが、中級レベルぐらいの問題なら解けます。
import java.util.ArrayList; /** * 数独を解く. */ public class Sudoku { static class マス { boolean 候補 = new boolean[9]; int 数字; // 1..9,未決定は0 マス(int num) { this.数字 = num; if (num == 0) { for (int i=0; i<候補.length; i++) { 候補[i] = true; } } } int get候補数() { if (数字 != 0) return 0; int count = 0; for (int i=0; i<候補.length; i++) { if (候補[i]) count++; } return count; } void 候補から外す(int num) { if (数字 == 0) 候補[num-1] = false; } /** 数字を決定する */ void set数字(int x) { 数字 = x; for (int i=0; i<候補.length; i++) { 候補[i] = false; } } boolean もし候補が1つなら確定させる() { if (数字 != 0) return false; int num = -1; for (int i=0; i<候補.length; i++) { if (! 候補[i]) { if (num == -1) num = i+1; else return false; } } set数字(num); return true; } public String toString() { String s = ""+数字+"["; for (int i=0; i<9; i++) { s += 候補[i] ? (""+(i+1)) : "-"; } return s + "]"; } } static class 盤面 { final int START = new int { 0, 3, 6 }; マス cells = new マス[9][9]; 盤面(int init) { for (int y=0; y<9; y++) { for (int x=0; x<9; x++) { cells[y][x] = new マス(init[y][x]); } } } /** * 9つのマスを返す。 * patternが'x'のときには横1列の9つのセルを返す。 * patternが'y'のときには縦1列の9つのセルを返す。 * patternが'3'のときには、3x3の9つのセルを返す。 * @param pattern 'x','y','3' * @param pos 0..8 * @return */ マス getマス(char pattern, int pos) { マス result = new マス[9]; switch (pattern) { case 'x': return cells[pos]; case 'y': for (int x=0; x<9; x++) { result[x] = cells[x][pos]; } return result; case '3': for (int dy=0; dy<3; dy++) { for (int dx=0; dx<3; dx++) { result[dy*3+dx] = cells[START[pos/3]+dy][START[pos%3]+dx]; } } return result; } return null; } boolean is全部解けた() { for (int y=0; y<9; y++) { for (int x=0; x<9; x++) { if (cells[y][x].数字 == 0) return false; } } return true; } int get未決定のマスの数() { int count = 0; for (int y=0; y<9; y++) { for (int x=0; x<9; x++) { if (cells[y][x].数字 == 0) count++; } } return count; } void print() { for (int y=0; y<9; y++) { for (int x=0; x<9; x++) { if (cells[y][x].数字 == 0) { System.out.print(" -"); } else { System.out.print(" "+cells[y][x].数字); } } System.out.println(); } } void dump() { for (int y=0; y<9; y++) { for (int x=0; x<9; x++) { System.out.print(cells[y][x]); } System.out.println(); } System.out.println("未決定のマスの数="+get未決定のマスの数()); } void 候補を絞る(int xpos, int ypos, int num) { // 横の列にはnumはない for (int pos=0; pos<9; pos++) { if (pos != xpos) cells[ypos][pos].候補から外す(num); if (pos != ypos) cells[pos][xpos].候補から外す(num); } int ystart = ypos / 3 * 3; int xstart = xpos / 3 * 3; for (int dy=0; dy<3; dy++) { for (int dx=0; dx<3; dx++) { cells[ystart+dy][xstart+dx].候補から外す(num); } } } void 候補を絞る() { // 数字が確定しているとき、その列、行、3x3の中にその数字はない。 for (int y=0; y<9; y++) { for (int x=0; x<9; x++) { if (cells[y][x].数字 != 0) { 候補を絞る(x, y, cells[y][x].数字); } } } // 数字が未確定で、候補が1つのとき、数字を確定させる。 for (int y=0; y<9; y++) { for (int x=0; x<9; x++) { cells[y][x].もし候補が1つなら確定させる(); } } // 数字が未確定で、その列、行、3x3の他のセルにその候補がなければ、それがその数字になる。 for (int pos=0; pos<9; pos++) { 候補が他になければ確定させる(getマス('x', pos)); 候補が他になければ確定させる(getマス('y', pos)); 候補が他になければ確定させる(getマス('3', pos)); } for (int pos=0; pos<9; pos++) { 共通部分にのみ候補があればそれ以外に候補はない(getマス('x', pos), getマス('3', pos/3)); 共通部分にのみ候補があればそれ以外に候補はない(getマス('x', pos), getマス('3', pos/3+3)); 共通部分にのみ候補があればそれ以外に候補はない(getマス('x', pos), getマス('3', pos/3+6)); 共通部分にのみ候補があればそれ以外に候補はない(getマス('y', pos), getマス('3', pos%3)); 共通部分にのみ候補があればそれ以外に候補はない(getマス('y', pos), getマス('3', pos%3+3)); 共通部分にのみ候補があればそれ以外に候補はない(getマス('y', pos), getマス('3', pos%3+6)); } } /** * 1[---------] * 2[---------] * 3[---------] * ?[---4-6789] * ?[---456789] ←5の候補はこのセルしかない。5に確定する。 * ?[---4-6789] * ?[---4-6789] * ?[---4-6789] * ?[---4-6789] */ void 候補が他になければ確定させる(マス cells) { // 1〜9の数字に対して確定しているかどうかと、候補の数を数える。 boolean determinated = new boolean[9]; int 候補数 = new int[9]; for (int i=0; i<9; i++) { if (cells[i].数字 != 0) { determinated[cells[i].数字-1] = true; } else { for (int b=0; b<9; b++) { if (cells[i].候補[b]) 候補数[b]++; } } } // 1〜9の数字に対して候補の数が1個なら、その数字に確定する。 for (int n=0; n<9; n++) { if (determinated[n]) continue; if (候補数[n] == 1) { // その候補を持つセルを確定させる for (int i=0; i<9; i++) { if (cells[i].数字 == 0 && cells[i].候補[n]) { cells[i].set数字(n+1); break; } } } } } /** * A A A A A A C C C * ? ? ? ? ? ? B B B * ? ? ? ? ? ? B B B * * ? ? ? ? ? ? ? ? ? * ? ? ? ? ? ? ? ? ? * ? ? ? ? ? ? ? ? ? * * ? ? ? ? ? ? ? ? ? * ? ? ? ? ? ? ? ? ? * ? ? ? ? ? ? ? ? ? * もし、ある候補がCの中にのみあってBの中になければ、Aの中にもない。 * 同様に、ある候補がCの中にのみあってAの中になければ、Bの中にもない。 * さらに具体的に。左がAのセル、右がBのセル、上3つがCの共通のセル。 * [123456789]左と同一セル * [123456789]左と同一セル * [123456789]左と同一セル * [-23456789][123456789] ←B側のセルから1の候補が外れる * [-23456789][123456789] ←B側のセルから1の候補が外れる * [-23456789][123456789] ←B側のセルから1の候補が外れる * [-23456789][123456789] ←B側のセルから1の候補が外れる * [-23456789][123456789] ←B側のセルから1の候補が外れる * [-23456789][123456789] ←B側のセルから1の候補が外れる * 一方から見て、共有するセル内にしかない候補があれば、 * それはもう一方の共有していないセルにはない。 * 上記の例では、候補1は共有セルにしかない。Aのみのセルには * 存在しない。従って、1は必ず共有セルにあることになり、 * Bのみのセルには1の候補は外すことができる。 */ void 共通部分にのみ候補があればそれ以外に候補はない(マス cellsA, マス cellsB) { // 共通部分と残りの部分に分ける ArrayList Aだけ = toList(cellsA); ArrayList Bだけ = toList(cellsB); ArrayList 共通 = new ArrayList(); for (int i=8; i>=0; i--) { Object test = Aだけ.get(i); if (Bだけ.contains(test)) { Aだけ.remove(test); Bだけ.remove(test); 共通.add(test); } } assert 共通.size() == 3; assert Aだけ.size() == 6; assert Bだけ.size() == 6; for (int n=1; n<=9; n++) { if (is確定したか(共通, n)) continue; if (is確定したか(Aだけ, n)) continue; if (is確定したか(Bだけ, n)) continue; if (has候補(共通, n)) { if (! has候補(Aだけ, n) && has候補(Bだけ, n)) { for (int i=0; i<9; i++) System.out.print(cellsA[i]); System.out.println(); for (int i=0; i<9; i++) System.out.print(cellsB[i]); System.out.println(); System.out.println("候補から外す(Bだけ, x)"+n); 候補から外す(Bだけ, n); } else if (! has候補(Bだけ, n) && has候補(Aだけ, n)) { System.out.println("候補から外す(Aだけ, x)"+n); 候補から外す(Aだけ, n); } } } } private boolean is確定したか(ArrayList cells, int n) { for (int i=0; icells) { ArrayList result = new ArrayList(); for (int i=0; i args) { // http://www.kojima-cci.or.jp/~fuji/numplace/problems/0010.html int 問題0010 = new int { {0,4,0, 6,0,0, 0,0,9}, {5,0,0, 7,0,0, 0,2,0}, {0,0,0, 8,0,0, 1,0,0}, {4,6,8, 0,0,7, 0,0,0}, {0,0,0, 0,3,0, 0,0,0}, {0,0,0, 2,0,0, 5,7,6}, {0,0,1, 0,0,8, 0,0,0}, {0,3,0, 0,0,5, 0,0,4}, {7,0,0, 0,0,1, 0,5,0} }; // http://www.kojima-cci.or.jp/~fuji/numplace/problems/0004.html int 問題0004 = new int { {6,8,0, 0,0,0, 0,0,0}, {5,0,0, 7,1,0, 0,3,0}, {0,0,0, 4,0,0, 0,0,2}, {0,0,0, 0,0,9, 1,0,0}, {0,0,4, 0,0,0, 6,0,0}, {0,0,9, 8,0,0, 0,0,0}, {2,0,0, 0,0,3, 0,0,0}, {0,1,0, 0,2,6, 0,0,5}, {0,0,0, 0,0,0, 0,4,9} }; // http://www.kojima-cci.or.jp/~fuji/numplace/problems/0024.html int 問題0024 = new int { {0,0,0, 0,0,7, 2,0,0}, {0,6,1, 0,2,0, 0,8,0}, {2,0,0, 9,0,0, 0,3,0}, {7,0,0, 0,0,0, 4,0,0}, {0,9,0, 0,0,0, 0,6,0}, {0,0,4, 0,0,0, 0,0,9}, {0,2,0, 0,0,3, 0,0,4}, {0,3,0, 0,6,0, 1,7,0}, {0,0,9, 7,0,0, 0,0,0}, }; // ひな形 int 問題0000 = new int { {0,0,0, 0,0,0, 0,0,0}, {0,0,0, 0,0,0, 0,0,0}, {0,0,0, 0,0,0, 0,0,0}, {0,0,0, 0,0,0, 0,0,0}, {0,0,0, 0,0,0, 0,0,0}, {0,0,0, 0,0,0, 0,0,0}, {0,0,0, 0,0,0, 0,0,0}, {0,0,0, 0,0,0, 0,0,0}, {0,0,0, 0,0,0, 0,0,0}, }; if (new Sudoku().解く(new 盤面(問題0024))) System.out.println("楽勝だぜ!"); else System.out.println("むりポ"); } }
アルゴリズムは簡単。
最初はすべてのセルに1〜9の候補がありますが、問題の初期状態と、縦・横・3x3のセルを見ながら候補を絞り込んでいきます。
絞り込む方法をいくつか用意し、それを順番に数回繰り返しているだけです。
途中で全部解ければ終わり。
比較的簡単な方法しか実装していないけれど、結構賢いですよ。
それに速いです。