数独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();
}
posted @ 2019-12-28 23:10  追梦人啊  阅读(292)  评论(0)    收藏  举报