第4講 ヒントとなる数字が入っている数独を解く!
第4話 0行のセルリスト構造解析

#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 sudoku[n][n]; //解答用2次元配列(魔方陣の研究から始まったので本体をmとしてきた。
int mondai[n][n]; //sudoku[n][n]は解答用なので、問題用の2次元配列を用意した。
int sng = 1;//1ならば真
const int hnt = 30;
void syokika();//初期化
void hy();//結果をコンソール画面に表示する関数
void cellslstkaiseki();//第0行にあるセルのリスト構造解析
int lst[9][9][9];//セルリスト構造解析によって可能な数字を収容する3次元配列
int mx[9][9];//セルの数字候補の個数
int main() {
  clock_t hj, ow;
  hj = clock();
  syokika();
  f(0);
  cout << "ヒント数 = " << hnt << endl;
  cellslstkaiseki();
  ow = clock();
  if (sng == 1)cout << endl << "すべて正常でした。" << endl;
  cout << "計算時間は平均で" << (double)(ow - hj) / CLOCKS_PER_SEC << "秒です。" << endl;
  cout << "順列の場合の数は" << cn << "です。" << 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() {//結果をコンソール画面に表示する関数
  cout << endl;
  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 << " " << " " << " "; //空欄を作る
      }
      if (j % 3 == 2)cout << "|";//2本目3本目4本目の縦線
    }
    if (i % 3 == 2) {
      cout << endl;
      for (int j = 0; j < 26; j++)cout << " -"; //2本目3本目の横線
    }
    cout << endl;
  }  
}
void cellslstkaiseki() {
  for (int a = 0; a < n; a++) {
    if (mondai[0][a] == 0) {//解析対象を空欄のみとする
      for (int i = 0; i < n; i++)lst[0][a][i] = i + 1;//初期化 1から9までをリスト
      for (int i = 0; i < n; i++) {//行の条件によって候補数字を外す
        for (int j = 0; j < n; j++) {
          if (j != a) {//自己以外のセルによってセルは条件づけられている
            if (lst[0][a][i] == mondai[0][j]) {//過去に外されたものはlst[0][a][i]が0になっており、
                                 //等式が成り立たないから問題はない
              lst[0][a][i] = 0;//mondai[0][j]を候補数字から外す
            }
          }
        }
      }
      for (int i = 0; i < n; i++) {//列の条件によって候補数字を外す
        for (int j = 0; j < n; j++) {
          if (j != 0) {//自己以外のセルによってセルは条件づけられている
            if (lst[0][a][i] == mondai[j][a]) {//過去に外されたものはlst[a][b][i]が0になっており、
                                 //等式が成り立たないから問題はない
              lst[0][a][i] = 0;//mondai[j][a]を候補数字から外す
            }
          }
        }
      }
      //yとxはそれぞれブロックのトップ=一番上で一番左のセルの縦座標と横座標
      int y = 3 * (0 / 3);//aはmondai[0][a]の0
      int x = 3 * (a / 3);//bはmondai[0][a]のa
      //現在リスト構造解析を受けているセルは(a,b)でありブロック先頭の座標は(y,x)である
      //ブロック先頭の(0,0),(0,3),(0,6),(3,0),(3,3),(3,6),(6,0),(6,3),(6,6)と動いている。
      //それぞれのブロック先頭の座標をmonddai[a][b]の(a,b)から作り出さなければならない。
      for (int i = 0; i < n; i++) {//ブロックの条件によって候補数字を外す
        for (int j = 0; j < n; j++) {
          if (y + (j / 3) != 0 || x + (j % 3) != 0) {
           //自己以外のセルによってセルは条件づけられているから
           //自己自身は対象から外す
            if (lst[0][a][i] == mondai[y + (j / 3)][x + (j % 3)]) {
              //過去に外されたものはlst[0][a][i]が0になっており、
              //等式が成り立たないから問題はない
              lst[0][a][i] = 0;//mondai[y + (j / 3)][x + (j % 3)]を候補数字から外す
            }
          }
        }
      }
      mx[0][a] = 0;//候補数字数をいったん0に初期化しておく
      for (int i = 0; i < n; i++) {
        if (lst[0][a][i] > 0) {
          lst[0][a][mx[0][a]] = lst[0][a][i];
          mx[0][a]++;
        }
      }
      cout << endl;
      cout << "以下はセル(" << 0 << ", " << a << ")のリスト構造解析結果" << endl;
      for (int i = 0; i < mx[0][a]; i++) {
        cout << lst[0][a][i] << " ";
      }
      cout << endl;
      cout << "候補数字の個数" << mx[0][a] << endl;
    }
  }
}
void f(int s) {
  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までの整数を入力
    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++) {
          int hb; //部屋番号
          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]; //問題用の配列に代入
         }         
       }
     }
     free(gohr); //メモリ解放
     hy();//結果をコンソール画面に表示させる関数
     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:;
  }
}

次の課題は全体構造解析 = 全セルリスト構造解析を行うことです。


       ・
       ・
       ・

       ・
       ・
       ・

全体構造解析が正しいかわかるように

の図を行が変わる度に入れてください。

0行リスト構造解析と同じで、
全体構造解析の場合にも数字が入っているセルは解析対象から外してください。

第3話へ 第5話へ

トップへ