数独sudoku(四)实现之求解数独
大家好,今天完成求解数独这个子功能。
Github完整项目地址:https://github.com/surpasss/software-engineering
从上一节我们得知写文件很占时间,因此对于写文件,和之前一样,把每个待求解问题的答案保存在一个长字符串中,最后一次性写入。而对于读文件,同样可以一次性读入,用ifstream标准输入流的rdbuf接口,然后进行相应转换,详情可看此链接C++一次性读取文件全部内容。
根据详细设计中的思路比较轻松地完成编码后,接下来进行初步验证。首先是要有残局文件作为输入,但是目前还尚未构建残局,所以取了两个极端例子进行性能检测:1e6个全为0的数组(全需填充)和上一节生成的1e6个不需填充的终局。理论上应该是空格越多,耗时越长,因为没有碰壁可以搜得更深。
首先是1e6个不需填充的数组,截图为:

之后是1e6个全需填充的数组,截图为:

可以看到,运行时间分别为31.797秒和2分58秒,两者的读写文件的时间大致相当,后者多出的运行时间是花费在Check函数上,前者调用该函数基本不占时间。而Check函数也已经通过使用occupy数组优化过,因此我个人觉得性能还比较理想。
总结与所思
对于写代码,我有个地方犹豫了很久,就是如何读取残局。因为用scanf可以省去判断空格和换行,直接循环输入即可,代码结构简单明了。不过最后用长字符串保存、然后提取也很简单。另外还有个值得注意的坑点,就是一定要保证输入文件后面没有隐藏字符(txt文件不显示,也不是空格),否则运行会进入死循环,从而可能被误导认为是程序性能太差。我因为学艺不精在这里花了很多时间,唉,这就要求输入文件的格式严格正确,且可以求解。
最终代码:
#include "define.h"
static char buffer[165000000];//输出字符串
static int pos;//输出字符串的当前写入位置
int map[9][9];
bool flag;//是否已求解
int occupy[3][10][10];
int belong[9][9]=
{
{0, 0, 0, 1, 1, 1, 2, 2, 2},
{0, 0, 0, 1, 1, 1, 2, 2, 2},
{0, 0, 0, 1, 1, 1, 2, 2, 2},
{3, 3, 3, 4, 4, 4, 5, 5, 5},
{3, 3, 3, 4, 4, 4, 5, 5, 5},
{3, 3, 3, 4, 4, 4, 5, 5, 5},
{6, 6, 6, 7, 7, 7, 8, 8, 8},
{6, 6, 6, 7, 7, 7, 8, 8, 8},
{6, 6, 6, 7, 7, 7, 8, 8, 8},
};//对应小方格的序号
//变量初始化
void Init()
{
flag = false;
memset(occupy, 0, sizeof(occupy));
for(int i = 0; i < 9; i++)
for (int j = 0; j < 9; j++)
{
int tmp = map[i][j];
if (tmp)
{
occupy[0][i][tmp] = 1;//所在行号
occupy[1][j][tmp] = 1;//所在列号
occupy[2][belong[i][j]][tmp] = 1;//所在小方格序号
}
}
}
//n为小格子的序号(0-80),key为是否填充该小格子的数字(1-9)
bool Check(int n, int key)
{
int x = n / 9;//行号
int y = n % 9;//列号
if (occupy[0][x][key]) return false;
else if (occupy[1][y][key]) return false;
else if (occupy[2][belong[x][y]][key]) return false;
return true;//满足条件返回真
}
void DFS(int n)
{
if (n > 80)//已到达最后的小格子
{
flag = true;
return;
}
else if (map[n / 9][n % 9] != 0)//已填充
{
DFS(n + 1);
}
else
{
for (int i = 1; i <= 9; i++)
{
if (Check(n, i) == true)
{
int x = n / 9;
int y = n % 9;
//暂存原值
int a = occupy[0][x][i];
int b = occupy[1][y][i];
int c = occupy[2][belong[x][y]][i];
//更新
map[x][y] = i;
occupy[0][x][i] = 1;
occupy[1][y][i] = 1;
occupy[2][belong[x][y]][i] = 1;
DFS(n + 1);//递归
//已求解,层层回退
if (flag == true)
return;
//还原
map[x][y] = 0;
occupy[0][x][i] = a;
occupy[1][y][i] = b;
occupy[2][belong[x][y]][i] = c;
}
}
}
}
void SolveSudoku (char *path)
{
//打开文件
ifstream in (path);
ofstream out("sudoku.txt");
stringstream buf;
buf << in.rdbuf();//一次性读取
string s = buf.str();
int index = 0;//字符串s的当前下标
memset(buffer, ' ', sizeof(buffer));//用空格初始化输出字符串
pos = 0;
while (index + 161 <= s.length())
{
for(int i = 0; i < 9; i++)
for (int j = 0; j < 9; j++)
{
map[i][j] = s.at(index) - '0';
index += 2;//跳过空格和行末换行符
}
index += 1;//跳过待求解问题之间的换行符
Init();
DFS(0);
for (int i = 0; i < 9; i++)
{
for (int j = 0; j < 8; j++)
{
buffer[pos] = map[i][j] + '0';
pos += 2;//跳过空格
}
buffer[pos++] = map[i][8] + '0';
buffer[pos++] = '\n';
}
buffer[pos++] = '\n';
}
out.write(buffer, pos - 2);//不写入最后的换行符
cout << "Solve Finished!" << endl;
//关闭文件
in.clear();
in.close();
out.clear();
out.close();
}

浙公网安备 33010602011771号