算法介绍
本实验采用的是前向搜索的算法,选择约束最大的点作为下一个扩展点,并通过每选择一个点则更新各个点的限制域进行剪枝操作,加快速度。
将棋盘中每个点都设置为一个结构体,结构体的结构如下:
typedef struct {
int value; //表示此点的值
int limit; //表示此点受限制的个数
int f9[MAX+1]; //表示此点的限制域
}Node;
主要函数boolDFS(int zeros)逻辑如下:
1、若是参数zeros为0,则返回true;
2、寻找整个棋盘中没有值且限制个数最大的点Node0;
3、若是Node0的限制个数为9,则表示此点没有可填的值,则返回false;
4、循环寻找此点所能填的值,循环长度为1~9。循环的内部结构如下:
A 根据限制值域,若是循环因子i可被选为此点的值
a 将此点值置为i
b 参数zeros自减
c 更新整个棋盘所有点的限制值域和限制个数,相当于剪枝操作,避免不必要的搜索
d 递归向下调用DFS(zeros)
e 若是调用成功,则返回true
f 若是调用失败,zeros++,将此点的值重新置为0,更新整个棋盘的所有点的限制值域和限制个数,返回步骤4
5、若是循环完成,仍没有找到可填的值,则返回false。
主要函数voidupdate()的功能是更新整个棋盘的所有点的限制值域和限制个数,根据各点的value值进行更新。
主要函数voidinit()的功能是在每一行随机选择一个点,将此点的值设置为行号+1,赋值完所有行后,更新整个棋盘所有点的限制值域和限制个数。
生成完整数独的算法就是先使用init函数初始化9个点的值,在使用DFS函数进行数独求解,也即是说将数独的生成问题转化为数独的求解问题。
生成数独的算法是通过对完整数独进行挖洞。随机生成行列数,挖去此行列数所在的点的值,当挖去规定个数的值之后,更新棋盘所有点的限制值域和限制个数。
求解数独的算法是直接调用DFS函数进行求解。
Sudoku.h:
#ifndef SUDUKU_H
#define SUDUKU_H
#define MAX 9
typedef struct {
intvalue; //表示此点的值
intlimit; //表示此点受限制的个数
intf9[MAX+1]; //表示此点的限制域
}Node;
typedef struct {
introw; //行数
intcol; //列数
}Place;
Node map[MAX][MAX]; //棋局的81个点
void init(); //初始化,在每行随机挑选一个点,赋值为此行的行数+1
void update(); //更新棋局每个点的限制域和限制个数
Place getMAX(); //得到限制域最大的点的位置
bool DFS(int zeros); //深度优先搜索
void createSudo(int option); //创建数独并求解
void solveSudo(int holes); //求解数独
int digHole(int option); //在完整的数独上挖洞
void printSudo(); //打印数独
#endif
Sudoku.cpp:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include "Sudoku.h"
int main()
{
intoption;
printf("/*****************************************************/\n");
printf("/****** Choose the options: ******/\n");
printf("/****** 1.Create a simple sudo(35) ******/\n");
printf("/****** 2.Create a hard sudo(55) ******/\n");
printf("/****** 3.quit the game ******/\n");
printf("/*****************************************************/\n");
scanf("%d",&option);//从控制台输入选项
fflush(stdin);
if(option== 1 || option == 2){
createSudo(option);
}elseif(option == 3) {
exit(-1);
} else {
printf("illegaloption!!! quit the game\n");
}
return-1;
}
void createSudo(int option)
{
intholes;
inttimes = 1;
clock_tbegin,end;
memset(&map,0, sizeof(map)); //将棋盘所有点的值全置为0
init(); //初始化棋盘
DFS(72); //通过深度优先搜索求解数独
//打印初始的完整数独
printf("theinitial sudo:\n");
printSudo();
holes= digHole(option); //根据选项,将完整的数独挖去指定的个数
//打印出挖去指定个数的数独
printf("thesudo after dig the holes:\n");
printSudo();
printf("beginto solve the sudo we create.....\n");
begin= clock();
solveSudo(holes); //求解数独
end= clock();
//打印出求解的数独
printf("thesudo after solved:\n");
printSudo();
printf("thealgorithm cost %d ms to solve the sudo\n", end - begin);
}
bool DFS(int zeros)
{
introw, col;
Placeplace = getMAX(); //得到限制个数最大的点的位置
row= place.row;
col= place.col;
if(zeros== 0) //程序出口,若是所有点都已经赋值完成,返回true
returntrue;
if(map[place.row][place.col].limit== 9) //若是得到的点的限制数为9,则不能再扩展,返回false
returnfalse;
for(int i = 1; i < MAX + 1; i++) { //循环寻找此点所能填的值
if(map[row][col].f9[i]== 0) {
map[row][col].value= i;
zeros--;
update(); //更新棋盘所有点的限制值域,剪枝操作
if(!DFS(zeros)){ //递归调用深度优先算法去寻求下一点的解,若是下一个点不成功则需要回溯,重新选择当前点的值
zeros++;
map[row][col].value= 0;
update();
}else { //若是成功,返回true
returntrue;
}
}
}
returnfalse; //若是此点的所有值都不能满足,返回false
}
void update()
{
inti,j;
intcol, row;
//更新限制值域
for(i = 0; i < MAX; i++) { //i,j为棋盘各点的位置坐标
for(j = 0; j <MAX; j++) {
for(int k = 1; k < MAX + 1; k++) { //将i,j位置点的限制值域全部置为0
map[i][j].f9[k]= 0;
}
for(col = 0; col < MAX; col++) { //检查同一列,更新此点的限制值域
if(map[i][col].value!= 0)
map[i][j].f9[map[i][col].value]= 1;
}
for(row = 0; row < MAX; row++) { //检查同一行,更新此点的限制值域
if(map[row][j].value!= 0)
map[i][j].f9[map[row][j].value]= 1;
}
row= i / 3 * 3;
col= j / 3 * 3;
for(int r = 0; r < 3; r++) { //检查同一个3*3的小宫格,更新此点的限制值域
for(int c = 0; c < 3; c++) {
if(map[row+ r][col + c].value != 0)
map[i][j].f9[map[row+ r][col + c].value] = 1;
}
}
}
}
//更新限制个数
for(i = 0; i < MAX; i++) {
for(j = 0; j <MAX; j++) {
if(map[i][j].value!= 0) { //若是此点的值非0,则将此点的限制值域全置为1,限制个数置为9
for(int k = 1; k < MAX + 1; k++) {
map[i][j].f9[k]= 1;
}
map[i][j].limit= 9;
}else { //若是此点的值为0,根据限制值域更新限制个数
map[i][j].limit= 0;
for(intk = 1; k < MAX + 1; k++) {
if(map[i][j].f9[k] == 1)
map[i][j].limit++;
}
}
}
}
}
void init()
{
inti,col;
time_tt;
srand((unsigned)time(&t));
for(i= 0; i < MAX; i++) {
col= rand() % 9;
map[i][col].value= i + 1;
}
update();
}
int digHole(int option)
{
introw, col;
time_tt;
intholes;
srand((unsigned)time(&t));
if(option== 1) {
holes= 35;
}else {
holes=55;
}
for(int k = 0; k < holes; k++) {
row= rand() % 9;
col= rand() % 9;
if(map[row][col].value!= 0) {
map[row][col].value= 0;
}else {
k--;
}
}
update();
returnholes;
}
void solveSudo(int holes)
{
DFS(holes);
}
Place getMAX()
{
inti,j;
intlimit = 0;
Placemax;
for(i = 0; i < MAX; i++) {
for(j = 0; j <MAX; j++) {
if(map[i][j].limit> limit && map[i][j].value == 0) { //若是此点的limit值大于之前的limit值,而且此点的value为0
max.row= i;
max.col= j;
limit= map[i][j].limit;
}
}
}
returnmax;
}
void printSudo()
{
inti,j;
for(i = 0; i < MAX; i++) {
for(j = 0; j < MAX; j++) {
printf("%d\t",map[i][j].value);
}
printf("\n");
}
printf("\n");
}
最终结果:
总结:
求解难的数独的时间比简单数独的时间慢,而且由于难的数独挖去了55个数,而且挖洞的时候,我们也没有考虑挖去洞之后的解是否唯一,所以当挖去的数比较多的时候,重新求解的数独跟初始生成的数独有可能不一样,但是重新求解的数独是满足要求,这一点儿从图3可以看出。