第4講 ヒントとなる数字が入っている数独を解く!
第9話 完成したファイルを先に披露!
コピペできることは確認していますが、
コピペするとエラーするときはテキストファイルからコピペしてください。
#include<iostream>
#include<conio.h> //while (!_kbhit())を使うために必要
using namespace std;
void f(int s); //sは次元番号=部屋番号
const int n = 9; //16次数独や25次数独も考えてnと一般化した。
int cn = 0; //順列を数える変数
int cn1 = 0; //順列を数える変数
int sudoku[n][n]; //解答用2次元配列(魔方陣の研究から始まったので本体をmとしてきた。
int mondai[n][n]; //sudoku[n][n]は解答用なので、問題用の2次元配列を用意した。
int sng = 1;//1ならば真
const int hnt = 30;
const int tm = 81;
void syokika();
void hy();//結果をコンソール画面に表示する関数
void kyokusyokaiseki(int y, int x);//単セル解析(候補数字探索)
void nextcell(int g);//次に入力するセルを探索する関数
void sudokukaiho(int g);//数独を解くエンジン 部分構造解析を進めながら問題を解いていく
int lst[9][9][9];//セルリスト構造解析によって可能な数字を収容する3次元配列
int mx[9][9];//セルの数字候補の個数
int Y[81], X[81];//次に入力するセルのy座標とx座標
int main() {
clock_t hj, ow;
hj = clock();
syokika();
f(0);
cout << "ヒント数 = " << hnt << endl;
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (mondai[i][j] == 0)kyokusyokaiseki(i, j);
//セルリスト構造解析 = 単セル解析を積み重ねれば全体構造解析になる
}
}
sudokukaiho(hnt);//数独を解くエンジン 部分構造解析を進めながら問題を解いていく
ow = clock();
if (sng == 1)cout << endl << "すべて正常でした。" <<
endl;
cout << "計算時間は" << (double)(ow - hj) / CLOCKS_PER_SEC
<< "秒です。" << endl;
cout << "プロジェクト成功" << endl;
while (!_kbhit()); //待機させるための命令
return(0);
}
void syokika() {
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
sudoku[i][j] = 0;
mondai[i][j] = 0;
}
}
}
void hy() {
for (int i = 0; i < 26; i++)cout << " -"; //最初の横線
cout << endl;
for (int i = 0; i < n; i++) {
cout << "|";//最初の縦線
for (int j = 0; j < n; j++) {
if (mondai[i][j] > 0) {
cout << " " << mondai[i][j] << " ";
//2次元配列を2次元に並べる
}
else {
cout << " " << " " << " ";
//2次元配列を2次元に並べる
}
if (j % 3 == 2)cout << "|";//2本目3本目の縦線
}
if (i % 3 == 2) {
cout << endl;
for (int j = 0; j < 26; j++)cout << " -"; //2本目3本目の横線
}
cout << endl;
}
}
void kyokusyokaiseki(int y, int x) {//局所解析 = 単セルリスト構造解析
for (int i = 0; i < n; i++)lst[y][x][i] = i + 1;//初期化{1,2,3,4,5,6,7,8,9}とする
mx[y][x] = 0;//{1,2,3,4,5,6,7,8,9}を{2,4}等にするために0に初期化 再カウントのため
for (int i = 0; i < n; i++) {//mondai[y][x]と同じ行にあるセルからの影響を調べる
if (i != x) {//自分自身は対象にしない
if (mondai[y][i] > 0) {
for (int j = 0; j < n; j++) {
if (lst[y][x][j] == mondai[y][i])lst[y][x][j] = 0;
//mondai[y][i]と一致する数字を0にすることによって候補から外す
}
}
}
}
for (int i = 0; i < n; i++) {//mondai[y][x]と同じ列にあるセルからの影響を調べる
if (i != y) {
if (mondai[i][x] > 0) {
for (int j = 0; j < n; j++) {
if (lst[y][x][j] == mondai[i][x])lst[y][x][j] = 0;
//mondai[i][x]と一致する数字を0にすることによって候補から外す
}
}
}
}
for (int i = 0; i < n; i++) {//mondai[y][x]と同じブロックにあるセルからの影響を調べる
if (3 * (y / 3) + (i / 3) != y && 3 * (x / 3) + (i % 3) !=
x) {
if (mondai[3 * (y / 3) + (i / 3)][3 * (x / 3) + (i % 3)] > 0)
{
for (int j = 0; j < n; j++) {
if (lst[y][x][j] == mondai[3 * (y / 3) + (i / 3)][3 * (x / 3)
+ (i % 3)])lst[y][x][j] = 0;
//mondai[3 * (y / 3) + (i / 3)][3 * (x / 3) + (i % 3)]と一致する数字を
//0にすることによって候補から外す
}
}
}
}
for (int i = 0; i < n; i++) {
if (lst[y][x][i] > 0) {
lst[y][x][mx[y][x]] = lst[y][x][i];//例えば、{0, 2, 0, 4, 0, 0, 0, 0,
0}を{2, 4}と詰めて0を含めない
mx[y][x]++;
}
}
}
void nextcell(int g) {//次に入力するセルの座標を探索する関数
int mn = 10;
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (mondai[i][j] == 0) {//空欄のみをランキング対象にする
if (mx[i][j] < mn) {//<=でないことによって左及び上が優先される
mn = mx[i][j];
Y[g] = i;
X[g] = j;
}
}
}
}
}
void sudokukaiho(int g) {//数独を解くエンジン
nextcell(g);//座標(Y[g], X[g])の取得
for (int i = 0; i < mx[Y[g]][X[g]]; i++) {
mondai[Y[g]][X[g]] = lst[Y[g]][X[g]][i];
//候補の数字を代入 例えば、mondai[0][1]なら{2, 4}の2,4の順に代入する
for (int j = 0; j < n; j++)if (j != X[g])if (mondai[Y[g]][j] ==
0)kyokusyokaiseki(Y[g], j);
//x[Y[g]][X[g]]と同じ行にあるセルの単セル解析(候補数字探索)
for (int j = 0; j < n; j++)if (j != Y[g])if (mondai[j][X[g]] ==
0)kyokusyokaiseki(j, X[g]);
//x[Y[g]][X[g]]と同じ列にあるセルの単セル解析(候補数字探索)
int s = 3 * (Y[g] / 3);
int t = 3 * (X[g] / 3);
for (int j = 0; j < n; j++) {
if (s + (j / 3) != Y[g] && t + (j % 3) != X[g]) {//行解析と列解析と重複させないため
if (mondai[s + (j / 3)][t + (j % 3)] == 0) {
kyokusyokaiseki(s + (j / 3), t + (j % 3));
//x[Y[g]][X[g]]と同じブロックにあるセルの単セル解析(候補数字探索)
}
}
}
if (g + 1 < tm)sudokukaiho(g + 1);//内側部屋へ
if (cn1 == 1) {
return;//1つで来た時点で止める
}
if (g == tm - 1) {
hy();//解答表示
cn1 = 1;
}
if (i == mx[Y[g]][X[g]] - 1) {
//i < mx[Y[g]][X[g]] - 1)のときはiが1つ進んで上で単セル解析(候補数字探索)を行うので
//キャンセルは不要だが、最後だけはキャンセルをしなければならないので単セル解析(候補数字探索)
//を行う必要がある
//cout << "*-*-*-*-復元-*-*-*-*-*" << endl;
mondai[Y[g]][X[g]] = 0;
//cout << g << endl;
for (int j = 0; j < n; j++)if (j != X[g])if (mondai[Y[g]][j] ==
0)kyokusyokaiseki(Y[g], j);
for (int j = 0; j < n; j++)if (j != Y[g])if (mondai[j][X[g]] ==
0)kyokusyokaiseki(j, X[g]);
for (int j = 0; j < n; j++) {
if (s + (j / 3) != Y[g] && t + (j % 3) != X[g]) {
if (mondai[s + (j / 3)][t + (j % 3)] == 0) {
kyokusyokaiseki(s + (j / 3), t + (j % 3));
}
}
}
}
}
}
void f(int s) {//ヒント数0の数独を解く関数
int y = s / 9; //縦座標
int x = s % 9; //横座標
unsigned u = (unsigned)time(NULL);
srand(2); //シード整数値を現在の時刻から取得 後に2はuに戻す
int ii = rand() % 9; //始まりをランダムにする
for (int i = 0; i < n; i++) {
sudoku[y][x] = (i + ii) % n + 1; //2次元配列に1から9までの整数を入力
//現時点ではなぜかうまくいっていないが後に原因を解明予定
//例えば、ii = 5でi = 0ならば(5 + 0) % 9 + 1の計算から6になるはずなのに現実は1になっている
//この問題は後に改善する
if (x > 0) {
for (int j = 0; j < x; j++) {
if (sudoku[y][x] == sudoku[y][j])goto tobi; //行の重複を防ぐ
}
}
if (y > 0) {
for (int j = 0; j < y; j++) {
if (sudoku[j][x] == sudoku[y][x])goto tobi; //行の重複を防ぐ
}
}
if (y % 3 == 1) {
for (int j = 0; j < 3; j++) {
if ((x / 3) * 3 + j != x) {
if (sudoku[y][x] == sudoku[y - 1][(x / 3) * 3 + j])goto tobi;
//ブロックの重複を防ぐ
}
}
}
if (y % 3 == 2) {
for (int j = 0; j < 3; j++) {
if ((x / 3) * 3 + j != x) {
if (sudoku[y][x] == sudoku[y - 2][(x / 3) * 3 + j])goto tobi;
//ブロックの重複を防ぐ
if (sudoku[y][x] == sudoku[y - 1][(x / 3) * 3 + j])goto tobi;
//ブロックの重複を防ぐ
}
}
}
if (s + 1 < n * n)f(s + 1); //1つ奥の部屋に入室
if (cn == 1)return; //数独が1個できた時点で止める
if (sng == 0)return; //異常があった時点でプロジェクトを止める
if (s == n * n - 1) { //一番奥の部屋に到達
int hb; //部屋番号
int tbs[6] = { 11,13,17,19,23,29 }; //飛びの選択肢 素数であれば81と互いに素は保証されている
int st = rand() % 81; //始めの位置
int tb = tbs[rand() % 6]; //飛び
int h = 0; //可否の否
int* gohr = (int*)calloc(hnt, sizeof(int));
for (int t = 0; t < hnt; t++) {
gohr[t] = (st + t * tb) % 81;
}
for (int j = 0; j < n; j++) {
for (int k = 0; k < n; k++) {
hb = n * j + k; //部屋番号の再初期化 = 空欄の数字候補の個数の小さい順に入れる
int h = 0; //可否の否
for (int t = 0; t < hnt; t++) {
if (gohr[t] == hb) {
h = 1;//可否の可
break;
}
}
if (h == 1) {
mondai[j][k] = sudoku[j][k]; //問題用の配列に代入
}
}
}
hy();//問題表示
free(gohr); //メモリ解放
int p[9];//重複検査のための配列
int kr;
for (int j = 0; j < n; j++) {
for (int k = 0; k < n; k++)p[k] = 0;//初期化
kr = 1; //初期化
for (int k = 0; k < n; k++) {
p[sudoku[j][k] - 1] = 1;
}
for (int k = 0; k < n; k++) {
if (p[k] == 0) { //行の重複検査
cout << "×";
kr = 0;
sng = 0;
goto tobi1;
}
}
for (int k = 0; k < n; k++)p[k] = 0;//初期化
kr = 1; //初期化
for (int k = 0; k < n; k++) {
p[sudoku[k][j] - 1] = 1;
}
for (int k = 0; k < n; k++) {
if (p[k] == 0) { //列の重複検査
cout << "×";
kr = 0;
sng = 0;
goto tobi1;
}
}
for (int k = 0; k < n; k++)p[k] = 0;//初期化
kr = 1; //初期化
for (int k = 0; k < n; k++) {
p[sudoku[3 * (j / 3) + (k / 3)][3 * (j % 3) + (k % 3)] - 1] =
1;
}
for (int k = 0; k < n; k++) {
if (p[k] == 0) {//ブロックの重複検査
cout << "×";
kr = 0;
sng = 0;
goto tobi1;
}
}
}
tobi1:;
if (kr == 1)cout << "〇";
cn++;
if (cn == 1)return; //数独が1個できた時点でとめる
}
tobi:;
}
}
青色が変更を加えた場所です。
解説したいことはいっぱいあります。
Ⅰ.単セル解析(候補数字探索)で行った部分解析はなぜキャンセルという作業を必要としないのか。
Ⅱ.そもそもそのキャンセルとは何か。
第8話へ 第5講第1話へ
トップへ