初级数独破解

3 篇文章 0 订阅
#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.对于不能用上述方案解决的数独, 程序会陷入死循环

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值