プログラミング言語I 第13回 パズル2 数独(sudoku) ルール 解き方1

数独(sudoku)
„
日本発祥のパズル
„
プログラミング言語I 第13回
„
„
パズル2
ナンバープレース, Single Numbersなど様々
な名称がある
縦9個、横9個のマス目を使用
全てのマスに1~9の1桁の数字を入れる
2 3 4
数独の
問題例
9 1
4
埼玉大学工学部
電気電子システム工学科
伊藤 和人
„
横1行、縦1行には同じ数字は入らない
ボックスにも同じ数字は入らない
7
4
行
6
9
8
1
ボックス 2
3
5
9
8
6
5
7
Copyright © 2008 Kazuhito Ito
8
空きマスに
数字を入れる
ここに‘2’があるので、同じボックス、同じ行、
同じ列のマスには‘2’は入れられない
2 3 4
列
答え
2
1
2
いくつかのマスは
予め数字が入っている
解き方1
ルール
„
7 5
4
1 9
7 2
3
2
5
8
7
4
3
6
1
9
3
1
9
2
5
6
7
8
4
4
7
1
6
2
9
8
5
3
5
6
2
1
3
8
9
4
7
9
8
3
5
7
4
1
6
2
8
2
7
4
1
5
3
9
6
6
3
5
8
9
7
4
2
1
1
9
4
3
6
2
5
7
8
9 1
4
2
1
2
9
8
5
Copyright © 2008 Kazuhito Ito
7 5
4
1 9
7 2
3
‘2’が入れら
れないマス
6
7
8
Copyright © 2008 Kazuhito Ito
解き方1(続き1)
解き方1(続き2)
‘2’を入れられるのは、
このマスだけ ⇒ ここは‘2’に決定
2 3 4
2 3 4
9 1
4
ここに‘2’
2
1
2
9
8
5
Copyright © 2008 Kazuhito Ito
7 5
4
1 9
7 2
3
‘2’が入れら
れないマス
9 1
2
4
2
1
2
6
7
9
8
8
5
Copyright © 2008 Kazuhito Ito
7 5
4
1 9
7 2
3
6
7
8
‘2’が入れら
れないマス
ここに‘2’
解き方2
考え方
„
2 3 4
9 1
4
7 5
4
1 9
7 2
3
2
1
2
9
8
5
„
注目するマスと
同一行、同一列、
同一ブロックの
マス
6
7
8
‘3’以外は、行、列、ボックスのどこかに存在
⇒ このマスには‘3’しか入れられない ⇒ ‘3’に決定
Copyright © 2008 Kazuhito Ito
マスに関するデータを記録する構造体
typedef struct PlaceRec{
int bFixed;
int nValue;
要素aValue[1]~
int aValue[10];
aValue[9]を利用し、
} PLACE;
aValue[0]は使用しない
„
int bFixed;
マスの値が決定しているか否か
int nValue;
決定したマスの値
データ構造と初期化
9×9個のマスについて構造体を用意
PLACE Place[9][9];
„
レベル1の推論
ルールをそのまま適用して値の除外、
値の決定ができるもの
Copyright © 2008 Kazuhito Ito
„
„
初期化
int x, y, v;
for( x=0 ; x<9 ; x++ ){
for( y=0 ; y<9 ; y++ ){
Place[x][y].bFixed = 0;
値は未定
for( v=1 ; v<=9 ; v++ ){
Place[x][y].aValue[v] = 1;
}
}
1~9の全ての値が入れられる
}
Copyright © 2008 Kazuhito Ito
構造体メンバaValue[10]の意味
v=1~9について
aValue[v]=1 値vがマスに入れられる
aValue[v]=0 値vがマスに入れられない
例えば
aValue[1]=aValue[2]=・・・aValue[9]=0
aValue[4]のみ1の場合、
構造体のメンバ
int aValue[10]; 1~9の各値vが
マスに入れられるか否か
Copyright © 2008 Kazuhito Ito
„
「ここ」に入りえない値を除外する
「ここ」にしか入れられない値を見つける
データ構造(続き)
データ構造
„
„
基本は、各マスには全ての値が入りうる
マスの値は4と決定できる
他にも値の決め方あり(後述)
Copyright © 2008 Kazuhito Ito
データ初期化(続き)
„
予めマスに入っている値を設定
マス(1,0)
2 3 4
9 1
void FixOne(int x, int y, int v0)
{
int v;
for( v=1 ; v<=9 ; v++ ){
Place[x][y].aValue[v] = 0;
}
Place[x][y].aValue[v0] = 1;
Place[x][y].bFixed = 1;
Place[x][y].nValue = v0;
}
v0以外は
除外
値をv0に
FixOne( 1, 0, 2 ); /* マス(1,0)は2 */
その他のマスについても同様に初期化する
Copyright © 2008 Kazuhito Ito
マスの値を限定する(その1)
„
「ここ」に入りえない値を除外する
4
1
2
行y
2
5
9
このマスが値5に
決定した場合
1 9
7 2
行yの他のマスには
3
値5は入らない
void L1InferRow(int y, int v)
行方向の推論
{
int i;
for( i=0 ; i<9 ; i++ ){
if( Place[i][y].bFixed ) continue;
Place[i][y].aValue[v] = 0;
行yのマス(i,y)から
}
値vを除外
}
Copyright © 2008 Kazuhito Ito
マスの値を限定する(その3)
ボックス内の推論
void L1InferBox(int bx, int by, int v)
{
int i,j;
for( i=0 ; i<3 ; i++ ){
for( j=0 ; j<3 ; j++ ){
if( Place[bx+i][by+j].bFixed ) continue;
Place[bx+i][by+j].aValue[v] = 0;
}
ボックス(bx,by)内の
}
}
マスから値vを除外
ここまで、レベル1の推論
Copyright © 2008 Kazuhito Ito
考え方
„
„
„
基本は、各マスには全ての値が入りうる
「ここ」に入りえない値を除外する
「ここ」にしか入れられない値を見つける
レベル1の推論
ルールをそのまま適用して値の除外、
値の決定ができるもの
Copyright © 2008 Kazuhito Ito
マスの値を限定する(その2)
void L1InferColumn(int x, int v)
列方向の推論
{
int j;
for( j=0 ; j<9 ; j++ ){
if( Place[x][j].bFixed ) continue;
Place[x][j].aValue[v] = 0;
列xのマス(x,j)から
}
値vを除外
}
Copyright © 2008 Kazuhito Ito
マスの値を限定する(その4)
„
レベル1の推論を適用
void L1Infer()
{
int bx, by, i, j, v;
for( bx=0 ; bx<9 ; bx+=3 )
for( by=0 ; by<9 ; by+=3 ) マス(bx+i,by+j)は
for( i=0 ; i<3 ; i++ )
値vに確定している
for( j=0 ; j<3 ; j++ )
if( Place[bx+i][by+j].bFixed ){
v = Place[bx+i][by+j].nValue;
L1InferBox( bx, by, v ); // ボックス
L1InferRow( by+j, v );
// 行
L1InferColumn( bx+i, v ); // 列
}
}
Copyright © 2008 Kazuhito Ito
マスの値を決める(その1)
„
唯一入れられる値に決定する
for( x=0 ; x<9 ; x++ ){
マス(x,y)について
for( y=0 ; y<9 ; y++ ){
if( Place[x][y].bFixed ) continue;
k = 0;
for( v=1 ; v<=9 ; v++ ){
値1~9のうち
if( Place[x][y].aValue[v] ){
k++; v0 = v;
マスに入れられる
}
値を数える
}
if( k == 1 ){ /*マス(x,y)に入り得る値は唯一*/
FixOne( x, y, v0 );
唯一入れられる
}
値v0に決定する
}
}
Copyright © 2008 Kazuhito Ito
マスの値を決める(その2)
„
「ここ」にしか入れられない値を見つける
/* 行方向 */
値vについて
for( y=0 ; y<9 ; y++ ){
k = 0;
for( x=0 ; x<9 ; x++ ){
行yのマスのうち
if( Place[x][y].aValue[v] ){
値vを入れられる
k++; x0 = x;
マスを数える
}
}
if( k == 1 ){ /* 行yで値vを入れられるマスは唯一 */
if( ! Place[x0][y].bFixed ){
FixOne( x0, y, v );
マス(x0,y)を
}
値vに決定する
}
}
Copyright © 2008 Kazuhito Ito
マスの値を決める(その4)
„
「ここ」にしか入れられない値を見つける
/* ボックス */
値vについて
for( bx=0 ; bx<9 ; bx+=3 ){
for( by=0 ; by<9 ; by+=3 ){
ボックス内で
k = 0;
値vを入れられる
for( i=0 ; i<3 ; i++ )
マスを数える
for( j=0 ; j<3 ; j++ )
if( Place[bx+i][by+j].aValue[v] ){
k++; x0 = bx+i; y0 = by+j;
}
ボックス内で値vを
}
入れられるマスは唯一
if( k == 1 )
if( ! Place[x0][y0].bFixed )
マス(x0,y0)を
FixOne( x0, y0, v );
値vに決定する
}
Copyright © 2008 Kazuhito Ito
実行例
マスの値を決める(その3)
„
Copyright © 2008 Kazuhito Ito
マスの値を決定する
„
値を確定するとともに推論を行う
⇒ FixOne関数を修整
void FixOne(int x, int y, int v0)
{
int v;
for( v=1 ; v<=9 ; v++ ){
Place[x][y].aValue[v] = 0;
}
Place[x][y].aValue[v0] = 1;
マス(x,y)の値確定
Place[x][y].bFixed = 1;
Place[x][y].nValue = v0;
確定した値はv0
}
L1Infer();
レベル1の推論を行う
Copyright © 2008 Kazuhito Ito
マスの値を限定する(その5)
„
開始前
「ここ」にしか入れられない値を見つける
/* 列方向 */
値vについて
for( x=0 ; x<9 ; x++ ){
k = 0;
for( y=0 ; y<9 ; y++ ){
列xのマスのうち
if( Place[x][y].aValue[v] ){
値vを入れられる
k++; y0 = y;
マスを数える
}
}
if( k == 1 ){ /* 列xで値vを入れられるマスは唯一 */
if( ! Place[x][y0].bFixed ){
FixOne( x, y0, v );
マス(x,y0)を
}
値vに決定する
}
}
「ここ」に入りえない値を除外する
①このボックス
では値5は
3 2
ここに入る
1 8 6
1回目の
ルール適用
◎ここが5に
決まった
②このボックスでは
値5はこれらのマスには入らない
2回目の
ルール適用
レベル2の推論
Copyright © 2008 Kazuhito Ito
4
5
1 9
7 2
マスの値を限定する(その6)
行方向処理
値vについて
k = 0;
ボックス(bx,by)内の行jに
for( j=0 ; j<3 ; j++ ){
値vが入る可能性がある
for( i=0 ; i<3 ; i++ )
if( Place[bx+i][by+j].aValue[v] ) break;
if( i<3 ){
値vが入りうる行を数える
j0 = j; k++;
}
ボックス(bx,by)内で、値vは
}
行j0にのみ入る可能性がある
if( k == 1 ){
for( bx0=0 ; bx0<9 ; bx0+=3 ){
if( bx0 == bx ) continue;
for( i=0 ; i<3 ; i++ )
Place[bx0+i][by+j0].aValue[v] = 0;
}
横に並んだ他のボックスでは
}
行j0に値vは入ってはならない
Copyright © 2008 Kazuhito Ito
マスの値を限定する(その8)
„
「ここ」に入りえない値を除外する
void L2Infer()
レベル2の推論
{
int bx, by, bx0, by0;
int i,j,i0,j0;
int v, k;
for( by=0 ; by<9 ; by+=3 ){
for( bx=0 ; bx<9 ; bx+=3 ){
for( v=1 ; v<=9 ; v++ ){
行方向の推論(前々ページ)
列方向の推論(前ページ)
}
}
}
}
Copyright © 2008 Kazuhito Ito
改良版実行例
マスの値を限定する(その7)
列方向処理
値vについて
k = 0;
ボックス(bx,by)内の列iに
for( i=0 ; i<3 ; i++ )
値vが入る可能性がある
for( j=0 ; j<3 ; j++ ){
if( Place[bx+i][by+j].aValue[v] ) break;
if( j<3 ){
値vが入りうる列を数える
i0 = i; k++;
}
ボックス(bx,by)内で、値vは
}
列i0にのみ入る可能性がある
if( k == 1 ){
for( by0=0 ; by0<9 ; by0+=3 ){
if( by0 == by ) continue;
for( j=0 ; j<3 ; j++ )
Place[bx+i0][by0+j].aValue[v] = 0;
}
縦に並んだ他のボックスでは
}
列i0に値vは入ってはならない
Copyright © 2008 Kazuhito Ito
マスの値を決定する
„
値を確定するとともに推論を行う
void FixOne(int x, int y, int v0)
{
int v;
for( v=1 ; v<=9 ; v++ ){
Place[x][y].aValue[v] = 0;
}
Place[x][y].aValue[v0] = 1;
マス(x,y)の値確定
Place[x][y].bFixed = 1;
Place[x][y].nValue = v0;
確定した値はv0
}
L1Infer();
L2Infer();
レベル1, レベル2の推論を行う
Copyright © 2008 Kazuhito Ito
まとめ
„
„
„
「数独」を解くプログラム
問題を扱うためのデータ構造を考案
発想、考え方をC言語でプログラム化する
開始前
Copyright © 2008 Kazuhito Ito