数独,源自18世纪瑞士的一种数学游戏。是一种运用纸、笔进行演算的逻辑游戏。玩家需要根据9×9盘面上的已知数字,推理出所有剩余空格的数字,并满足每一行、每一列、每一个粗线宫(3*3)内的数字均含1-9,不重复。数独盘面是个九宫,每一宫又分为九个小格。在这八十一格中给出一定的已知数字和解题条件,利用逻辑和推理,在其他的空格上填入1-9的数字。使1-9每个数字在每一行、每一列和每一宫中都只出现一次,所以又称“九宫格”。
之所以我想实现数独,是因为在leetcode上发现了一道特别有意思的两道题题号是36和37,这两道题提供了解决数独的思路。
36题:
根据这三条规则
- 数字
1-9
在每一行只能出现一次。 - 数字
1-9
在每一列只能出现一次。 - 数字
1-9
在每一个以粗实线分隔的3x3
宫内只能出现一次
可以得出最终填完数独后的判断是否是一个有效的数独
进一步思考可以得出,如果在填入数字的时候时刻满足这三条规则,难么最终的结果一定是一个合法的数独
37题:
根据这道题可以得出解决的方法,使用回溯算法得到得到每一个位置应该的数字
思路很简单,实现起来并不容易(其中空的位置用*表示)
首先先实现check函数
我们通过坐标来进行判断,通过遍历一行、一列和一个方格的元素,判断是否有重复的数字
问题:在判断方格的时候,我们应该怎样才能遍历到方格中的每一个值?
如果我们已知了一个方格中的某一个数的坐标时即可得到其余每一个点的坐标,我们可以计算一个方格中最左上角的坐标,通过数独的特点可以发现,左上角的元素行坐标一定在1、4、7行,因此我们已知一个元素的坐标,可以通过带入公式x/3 * 3 + 1即可算出结果
bool every_check(int u , char cur)
{
int x , y;
if(u % 9 == 0) x = u / 9 , y = 9;
else x = u / 9 + 1 , y = u % 9;
for(int i = 1;i <= 9;i ++)
if(cur == problem[x][i]) return false;
for(int i = 1;i <= 9;i ++)
if(cur == problem[i][y]) return false;
//一个方格的第一个数的横坐标一定在 1、4、7
int sqr_first_pos = (x - 1) / 3 * 3 + 1;
//一个方格的第一个数的纵坐标一定也在 1、4、7
int sqr_second_pos = (y - 1) / 3 * 3 + 1;
for(int i = sqr_first_pos;i <= sqr_first_pos + 2;i ++)
for(int j = sqr_second_pos;j <= sqr_second_pos + 2;j ++)
if(problem[i][j] == cur) return false;
return true;
}
然后就是回溯算法
第一种方法,直接通过坐标进行遍历
首先先定义一个pair表示坐标,再定义一个向量表示上下左右移动,然后对于每一个坐标进行判断后,最终如果可以到达最后一个坐标然后每一个空都遍历过了即可确定就是最终答案,然而这样做的化时间复杂度会达到恐怖的O(9^m)(m为空的个数),因此这样一定无法在有限时间内完成任务
void dfs_error(PII pos , int u)
{
if(flag) return ;
int x = pos.first , y = pos.second;
if(x < 0 || x > 9 || y < 0 || y > 9) return ;
if(u > 99)
{
if(check())
{
for(int i = 1;i <= 9;i ++)
for(int j = 1;j <= 9;j ++)
solution[i][j] = problem[i][j];
flag = true;
}
return ;
}
for(int j = 0;j < 4;j ++)
{
int tx = dx[j] + x;
int ty = dy[j] + y;
PII t = {tx , ty};
//cout << tx << " " << ty << endl;
if(st[tx * 10 + ty]) continue;
if(problem[x][y] != '*')
{
st[x * 10 + y] = true;
dfs(t , tx * 10 + ty);
st[x * 10 + y] = false;
}
else
{
for(int i = 1;i <= 9;i ++)
{
problem[x][y] = i + '0';
st[x * 10 + y] = true;
dfs(t , tx * 10 + ty);
st[x * 10 + y] = false;
problem[x][y] = '*';
}
}
}
}
第二种方法
优化:我们可以发现数独总共一共有81个空,可以通过遍历从1~81,得到每一个位置的坐标,这样优化,可以降低时间复杂度,完美解决问题
void dfs(int u)
{
if(flag) return ;
int x , y;
if(u % 9 == 0) x = u / 9 , y = 9;
else x = u / 9 + 1 , y = u % 9;
if(u > 81)
{
for(int i = 1;i <= 9;i ++)
for(int j = 1;j <= 9;j ++)
solution[i][j] = problem[i][j];
flag = true;
return ;
}
//print();
//cout << u << " " << x << " " << y << endl;
if(problem[x][y] != '*') dfs(u + 1);
else
{
for(int i = 1;i <= 9;i ++)
{
char ch = i + '0';
if(every_check(u , ch))
{
problem[x][y] = ch;
dfs(u + 1);
problem[x][y] = '*';
}
}
}
}
最后,我们最后再重新判断一遍是否是一个有效的数独即可(其实可以不用实现,使用check函数即可完成操作,这里就不再赘述)
bool check_sqr(int type , int i)
{
unordered_set<char>se;
for(int x = 1 + type;x <= 3 + type;x ++)
{
int y = x + i;
if(x == 1 + type)
{
for(int j = y;j <= y + 2;j ++)
if(se.count(problem[x][j])) return false;
else se.insert(problem[x][j]);
}
else if(x == 2 + type)
{
for(int j = y - 1;j <= y + 1;j ++)
if(se.count(problem[x][j])) return false;
else se.insert(problem[x][j]);
}
else
{
for(int j = y;j >= y - 2;j --)
if(se.count(problem[x][j])) return false;
else se.insert(problem[x][j]);
}
}
return true;
}
bool total_check()
{
/**一行不能有重复的数**/
unordered_set<char>se;
for(int i = 1;i <= 9;i ++)
{
for(int j = 1;j <= 9;j ++)
if(se.count(problem[i][j])) return false;
else se.insert(problem[i][j]);
se.clear();
}
/**一列不能有重复的数**/
for(int i = 1;i <= 9;i ++)
{
for(int j = 1;j <= 9;j ++)
if(se.count(problem[j][i])) return false;
else se.insert(problem[j][i]);
se.clear();
}
/**每一个方格不能有重复的数**/
for(int i = 6;i >= -6;i -= 3)
{
if(i == 6)
{
if(!check_sqr(0 , i)) return false;
}
else if(i == 3)
{
if(!check_sqr(0 , i)) return false;
if(!check_sqr(3 , i)) return false;
}
else if(i == 0)
{
if(!check_sqr(0 , i)) return false;
if(!check_sqr(3 , i)) return false;
if(!check_sqr(6 , i)) return false;
}
else if(i == -3)
{
if(!check_sqr(3 , i)) return false;
if(!check_sqr(6 , i)) return false;
}
else
{
if(!check_sqr(6 , i)) return false;
}
}
return true;
}
主函数:即将数独读入即可
用文件的读写即可
int main()
{
ifstream ifs;
ifs.open("problem3.txt" , ios::in);
string buf;
int cnt = 1;
while(getline(ifs , buf))
{
for(int i = 0;i < buf.size();i ++)
problem[cnt][i + 1] = buf[i];
cnt ++;
}
//exit(0);
dfs(1);
for(int i = 1;i <= 9;i ++)
{
for(int j = 1;j <= 9;j ++)
cout << solution[i][j] << " ";
cout << endl;
}
return 0;
}
给出样例:
8*9**6*5*
***1****8
*****3*7*
**6***3**
***259***
**2***4**
*7*4*****
9****8***
*4*9**6*1
结果:
8 3 9 7 2 6 1 5 4
6 5 7 1 9 4 2 3 8
4 2 1 5 8 3 9 7 6
7 9 6 8 4 1 3 2 5
3 1 4 2 5 9 8 6 7
5 8 2 6 3 7 4 1 9
1 7 8 4 6 2 5 9 3
9 6 5 3 1 8 7 4 2
2 4 3 9 7 5 6 8 1
最终我想将这个写成应用,应该这个系列应该还会更新