#ifndef __SUDOKU_H__
#define __SUDOKU_H__
#include <wtypes.h>
struct Region
{
int index; // 原始下标
int count; // 已填个数
Region() : index(0), count(0)
{
}
bool operator < (const Region & rhs) const // std::sort用到
{
return (count < rhs.count);
}
};
struct Coord
{
int x;
int y;
Coord(int i = 0, int j = 0) : x(i), y(j)
{
}
Coord(const Coord & rhs) : x(rhs.x), y(rhs.y)
{
}
Coord & operator = (const Coord & rhs)
{
x = rhs.x;
y = rhs.y;
return *this;
}
};
class Sudoku
{
public:
Sudoku();
private:
void init();
void solve();
void show(int inew = -1, int jnew = -1);
private:
int get_first_try_region_index();
int get_next_try_region_index();
void get_region_unfilled(int region_index, Coord unfilled_coord[], int & coord_size, int unfilled_num[], int & num_size);
bool try_fill_num(int num, Coord unfilled_coord[], int & coord_size);
bool try_fill_coord(int i, int j, int unfilled_num[], int & num_size);
bool try_fill_region(int region_index);
private:
void updata_region_sequence();
int get_try_region_index();
bool try_fill_num_in_coord(int num, int i, int j);
void fill_data(int i, int j, int num);
private:
int m_data[9][9];
Region m_region[27];
int m_region_fill_count[27];
int m_try_region_index;
int m_incomplete_region_count;
HANDLE m_std_out_handle;
};
#endif
#include <fstream>
#include <iostream>
#include <algorithm>
using std::sort;
using std::cout;
using std::endl;
using std::cerr;
using std::ifstream;
#include <cassert>
#include <conio.h>
#include <windows.h>
#include "shudu.h"
/* for the damn vc6 */
#define for if (false) ; else for
Sudoku::Sudoku()
{
init();
}
int Sudoku::get_first_try_region_index()
{
// 取已填充数字最多的区间的原始下标(不计填满的)
m_try_region_index = m_incomplete_region_count - 1;
return get_try_region_index();
}
int Sudoku::get_next_try_region_index()
{
// 取已填充数字其次多的区间的原始下标(不计填满的)
--m_try_region_index;
return get_try_region_index();
}
/* 获取区间region_index中未填充的数字与未填充的坐标 */
void Sudoku::get_region_unfilled(int region_index, Coord unfilled_coord[], int & coord_size, int unfilled_num[], int & num_size)
{
coord_size = 0;
num_size = 0;
// 可填入的数字
int num[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
if (region_index < 9) { // 行区间
for (int j = 0; j < 9; ++j) {
if (0 == m_data[region_index][j]) { // 记录未填区间
unfilled_coord[coord_size].x = region_index;
unfilled_coord[coord_size].y = j;
++coord_size;
}
else { // 从可填充数字是去除数字m_data[region_index][j]
num[m_data[region_index][j]] = 0;
}
}
}
else if (region_index < 18) { // 列区间
region_index -= 9;
for (int i = 0; i < 9; ++i) {
if (0 == m_data[i][region_index]) { // 记录未填区间
unfilled_coord[coord_size].x = i;
unfilled_coord[coord_size].y = region_index;
++coord_size;
}
else { // 从可填充数字是去除数字m_data[i][region_index]
num[m_data[i][region_index]] = 0;
}
}
}
else { // 小九宫格区间
region_index -= 18;
for (int i = 3 * (region_index / 3), it = 0; it < 3; ++i, ++it) {
for (int j = 3 * (region_index - 3 * (region_index / 3)), jt = 0; jt < 3; ++j, ++jt) {
if (0 == m_data[i][j]) { // 记录未填区间
unfilled_coord[coord_size].x = i;
unfilled_coord[coord_size].y = j;
++coord_size;
}
else { // 从可填充数字是去除数字m_data[i][j]
num[m_data[i][j]] = 0;
}
}
}
}
for (int index = 0; index < 10; ++index) {
if (0 != num[index]) { // 记录未填数字
unfilled_num[num_size] = num[index];
++num_size;
}
}
// 区间中未填区间与未填数字的数量必定相等
assert(coord_size == num_size);
}
/* 尝试将数字填入各个坐标, 如果能唯一定位坐标则成功 */
bool Sudoku::try_fill_num(int num, Coord unfilled_coord[], int & coord_size)
{
int i = -1;
int j = -1;
int fill_index = -1;
for (int index = 0; index < coord_size; ++index) {
const Coord & coord = unfilled_coord[index];
// 尝试将数字num填入坐标(coord.x, coord.y)
if (try_fill_num_in_coord(num, coord.x, coord.y)) {
if (-1 == fill_index) { // 第一个可填充坐标
i = coord.x;
j = coord.y;
fill_index = index;
}
else { // 第二个可填充坐标
return false; // 不能唯一确定坐标, 尝试填充失败
}
}
}
// 如果输入数据正确, 数字一定可填充在unfilled_coord的某个坐标
assert(-1 != fill_index);
// 填充
fill_data(i, j, num);
// 从未填坐标数组中去除刚填成功的坐标
--coord_size;
Coord tmp_coord(unfilled_coord[coord_size]);
unfilled_coord[coord_size] = unfilled_coord[fill_index];
unfilled_coord[fill_index] = tmp_coord;
return true;
}
/* 尝试在坐标(i, j)中填入各个数字, 如果能唯一确定数字则成功 */
bool Sudoku::try_fill_coord(int i, int j, int unfilled_num[], int & num_size)
{
int num = -1;
int fill_index = -1;
for (int index = 0; index < num_size; ++index) {
// 尝试将数字unfilled_num[index]填入坐标(i, j)
if (try_fill_num_in_coord(unfilled_num[index], i, j)) {
if (-1 == fill_index) { // 第一个可填数字
num = unfilled_num[index];
fill_index = index;
}
else { // 第二个可填数字
return false; // 不能唯一确定数字, 尝试填充失败
}
}
}
// 如果输入数据正确, unfilled_num中一定有数字可填充在坐标(i, j)
assert(-1 != fill_index);
// 填充
fill_data(i, j, num);
// 从未填数字数组中去除刚填成功的数字
--num_size;
int tmp_num(unfilled_num[num_size]);
unfilled_num[num_size] = unfilled_num[fill_index];
unfilled_num[fill_index] = tmp_num;
return true;
}
/* 尝试填充区间region_index */
bool Sudoku::try_fill_region(int region_index)
{
int coord_size = 0;
Coord unfilled_coord[9]; // 记录未填坐标
int num_size = 0;
int unfilled_num[9]; // 记录未填数字
// 获取未填坐标与未填数字
get_region_unfilled(region_index, unfilled_coord, coord_size, unfilled_num, num_size);
bool all_failed = true;
while (true) {
bool first_loop = all_failed;
bool ever_success = false;
for (int index = num_size - 1; index >= 0; --index) {
// 尝试将数字填入各个坐标, 如果能唯一定位坐标则成功
if (try_fill_num(unfilled_num[index], unfilled_coord, coord_size)) {
// 从未填数字数组中去除刚填成功的数字
--num_size;
int tmp_num(unfilled_num[num_size]);
unfilled_num[num_size] = unfilled_num[index];
unfilled_num[index] = tmp_num;
all_failed = false;
ever_success = true;
}
}
if (!first_loop && !ever_success) {
break;
}
ever_success = false;
for (int index = coord_size - 1; index >= 0; --index) {
const Coord & coord = unfilled_coord[index];
// 尝试在坐标中填入各个数字, 如果能唯一确定数字则成功
if (try_fill_coord(coord.x, coord.y, unfilled_num, num_size)) {
// 从未填坐标数组中去除刚填成功的坐标
--coord_size;
Coord tmp_coord(unfilled_coord[coord_size]);
unfilled_coord[coord_size] = unfilled_coord[index];
unfilled_coord[index] = tmp_coord;
all_failed = false;
ever_success = true;
}
}
if (!ever_success) {
break;
}
}
return !all_failed;
}
void Sudoku::updata_region_sequence()
{
m_incomplete_region_count = 0; // 未填满的区间个数
for (int region_index = 0; region_index < 27; ++region_index) {
if (m_region_fill_count[region_index] < 9) { // 记录未填满的区间信息
m_region[m_incomplete_region_count].index = region_index; // 区间下标
m_region[m_incomplete_region_count].count = m_region_fill_count[region_index]; // 已填数字个数
++m_incomplete_region_count;
}
}
sort(m_region, m_region + m_incomplete_region_count); // 按已填个数排序未满区间信息
}
int Sudoku::get_try_region_index()
{
if (m_try_region_index < 0) {
return -1;
}
else {
return (m_region[m_try_region_index].index); // 得到区间m_try_region_index的原始下标
}
}
/* 尝试在(i, j)处填充num */
bool Sudoku::try_fill_num_in_coord(int num, int i, int j)
{
for (int k = 0; k < 9; ++k) {
if (m_data[i][k] == num) { // 所在于已有num
return false;
}
}
for (int k = 0; k < 9; ++k) {
if (m_data[k][j] == num) { // 所在列已有num
return false;
}
}
// 得到所在小九宫格的左上角数字的下标
i = 3 * (i / 3);
j = 3 * (j / 3);
for (int it = 0; it < 3; ++it) {
for (int jt = 0; jt < 3; ++jt) {
if (m_data[i + it][j + jt] == num) { // 所在小九宫格已有num
return false;
}
}
}
return true;
}
void Sudoku::fill_data(int i, int j, int num)
{
m_data[i][j] = num; // 填充数字
++m_region_fill_count[i]; // 填充数字所在行区间
++m_region_fill_count[9 + j]; // 填充数字所在列区间
++m_region_fill_count[18 + 3 * (i/3) + (j/3)]; // 填充数字所在小九宫格区间
show(i, j); // 打印
getch(); // 等一下
}
void Sudoku::init()
{
m_std_out_handle = GetStdHandle(STD_OUTPUT_HANDLE);
ifstream ifs("C:\\sudoku.txt");
if (!ifs) {
cerr << "open c:\\sudoku.txt failed..." << endl;
exit(-1);
}
int count = 0;
ifs >> count; // 数独盘面个数
while (count-- > 0) {
system("cls");
// 读入数独盘面
for (int i = 0; i < 9; ++i) {
for (int j = 0; j < 9; ++j) {
ifs >> m_data[i][j];
}
}
// 初始化各区间已填数字个数
for (int region_index = 0; region_index < 27; ++region_index) {
m_region_fill_count[region_index] = 0;
}
for (int i = 0; i < 9; ++i) {
for (int j = 0; j < 9; ++j) {
if (0 != m_data[i][j]) { // 统计各行/列/小九宫格, 暂统称为: 区间(region)
++m_region_fill_count[i]; // 统计各行的已填数字个数
++m_region_fill_count[9 + j]; // 统计各列已填数字个数
++m_region_fill_count[18 + 3 * (i/3) + (j/3)]; // 统计各小九宫格已填数字个数
}
}
}
show(); // 打印原始的数独盘面
getch(); // 等一下
solve(); // 尝试填数字, 每填一个数字会打印一次
show(); // 打印完成的数独盘面
}
ifs.close();
}
void Sudoku::solve()
{
updata_region_sequence(); // 以已填充数字个数排列区间下标
int index = get_first_try_region_index(); // 得到已填充数字最多的区间下标(不含满的)
while (index >= 0) { // 仍有区间未填满
// 尝试填充这个区间中的空白, 只要有空白填充成功都会返回true
if (try_fill_region(index)) {
updata_region_sequence(); // 重排区间下标
index = get_first_try_region_index(); // 得到已填最多的区间下标(不含满的)
}
else {
index = get_next_try_region_index(); // 得到其次个数的区间的下标
}
}
}
void Sudoku::show(int inew, int jnew)
{
WORD line_attributes = FOREGROUND_INTENSITY | FOREGROUND_GREEN; // 绿色加强
WORD text_attributes = FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_BLUE; // 品红加强
WORD special_attributes = FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_GREEN; // 黄色加强
SetConsoleTextAttribute(m_std_out_handle, line_attributes);
cout << endl << " --- --- --- --- --- --- --- --- --- " << endl;
for (int i = 0; i < 9; i++) {
cout << " | ";
for (int j = 0; j < 9; j++) {
if (0 == m_data[i][j]) {
cout << " ";
}
else {
WORD attributes = text_attributes;
if (inew == i && jnew == j) { // 新填充的数字用黄色字打印
attributes = special_attributes;
}
SetConsoleTextAttribute(m_std_out_handle, attributes);
cout << m_data[i][j];
SetConsoleTextAttribute(m_std_out_handle, line_attributes);
}
cout << " | ";
}
cout << endl << " --- --- --- --- --- --- --- --- --- " << endl;
}
cout << endl;
}
int main()
{
Sudoku shudu;
return 0;
}
sudoku.txt
1
1 6 5 0 0 0 0 0 0
0 4 8 0 3 1 6 0 5
2 9 0 5 7 6 0 0 1
4 0 7 0 6 3 0 0 0
6 0 0 2 0 4 0 0 7
0 0 0 7 1 0 3 0 4
8 0 0 3 9 7 0 1 6
3 0 6 1 8 0 4 7 0
0 0 0 0 0 0 5 3 8
估计只能搞定初级的数独
解答原理:
1.找填充得最饱满的行/列/九宫格, 尝试填充
2.将某个数字填充到指定的行/列/九宫格中所有未填的位置, 如果有且只有一个位置可以, 尝试成功
3.在指定的行/列/九宫格中填入各个数字, 如果有且只有一个数字可以, 尝试成功
4.如果2, 3都不成功, 找其次饱满的尝试
5.如果尝试成功, 重新查找最饱满的尝试(不计排除已填满的)
代码缺陷:
1.未对输入数据做检查
2.对于不能用上述方案解决的数独, 程序会陷入死循环