PS:此为程序初稿,有很多地方需要修复,比如错误输入后的Error检测,数组溢出检测等。
一般而言,计算机解决数独问题主要是采用回溯法,即通过不断地尝试及回溯,将函数不断递归,最终得到答案。然而递归和回溯将产生大量的计算,对于一些难度较大的数独问题,会耗费大量的时间。
这个程序的优化方案在于将普通人解决数独问题的思想策略加入到计算机的决策中。首先介绍各个变量的意义:
board[9][9]:数独的表格
flag[9][9]:判断此格数据是否可填写、可修改
c[9][9][9]:判断此格内是否可填某指定数字,每一格都有9个空位。(0标记:不可填写;1标记:可填写;2及以上标记:由于其他格已占据,无法填写)
count[9][9]:记录每一个位置可填的数字个数
min_i,min_j:记录每一步填写的坐标位置
整个程序的关键在于c[9][9][9]矩阵的处理。每填入/删除一个数字时,都需要将该填入的数字对附近所有空白状态格子的影响进行计算处理,如果某个位置中该数字是否可填写的状态发生改变,就修改count[9][9]矩阵中的计数值。由于修改过程中格子的状态会发生变化,min_i,min_j的坐标记录也是必需的。
下面简单介绍函数
judge函数:判断该格填入的数字是否符合要求
output函数:输出
run函数:输入坐标位置及输入的数字,进行回溯运算。其中在每次填入/删除过程中注意修改其影响到的所有格子的c矩阵标记,首先判断格子是否可填,然后将其+/-1,判断其可填状态是否发生改变(1→2/2→1时,需要修改count矩阵的计数值)在寻找下一个填写的格子时,首先找出count矩阵中的最小值,若其不为0,则可进入下一次迭代,反复循环。
prenum函数:输入数字
precount函数:预处理所有标记矩阵
主函数:在输入、预处理结束后,首先对flag矩阵标记进行处理,然后找出第一个可填数字的数量最少的格子,然后开始进入run函数
#include <iostream>
#include <stdlib.h>
#include <iomanip>
using namespace std;
int board[9][9]={0};
int flag[9][9]={0};
int c[9][9][9]={0};
int count[9][9]={0};
int min_i[80]={0};
int min_j[80]={0};
int counter;
int re=0;
bool judge(int x ,int y)
{
int i,j,i1,j1;
if (board[x][y]>9)
return false;
for (i=0;i<9;i++)
{
if (i!=x)
{
if (board[i][y] == board[x][y])
return false;
}
if (i!=y)
{
if (board[x][i] == board[x][y])
return false;
}
}
i1=(x/3)*3;
j1=(y/3)*3;
for (i=i1; i<i1+3; i++)
for (j=j1; j<j1+3 ;j++)
{
if (i!=x && j!=y)
if (board[i][j] == board[x][y])
return false;
}
return true;
}
void output()
{
for (int i=0;i<9;i++)
{
cout<<endl<<endl;
for (int j=0;j<9;j++)
{
cout<<setw(3)<<board[i][j]<<" ";
}
}
cout<<endl<<endl;
}
void run(int x, int y,int number)
{
int i,j,i1,j1,min;
counter++;
i1=(x/3)*3;
j1=(y/3)*3;
board[x][y]=number;
flag[x][y]=2;
for (i=0;i<9;i++)
{
if (i!=x && flag[i][y]==0)
{
if (c[i][y][number-1]>0)
{
if (c[i][y][number-1]==1)
count[i][y]--;
c[i][y][number-1]++;
}
}
if (i!=y && flag[x][i]==0)
{
if (c[x][i][number-1]>0)
{
if (c[x][i][number-1]==1)
count[x][i]--;
c[x][i][number-1]++;
}
}
}
for (i=i1; i<i1 +3 ;i++)
for (j=j1;j<j1+3;j++)
if (i!=x && j!=y && flag[i][j]==0)
{
if (c[i][j][number-1]>0)
{
if (c[i][j][number-1]==1)
count[i][j]--;
c[i][j][number-1]++;
}
}
min_i[counter]=0;
min_j[counter]=0;
min=9;
for (i=0;i<9;i++)
for (j=0;j<9;j++)
{
if (flag[i][j]==0)
{
if (count[i][j]<min)
{
min_i[counter]=i;
min_j[counter]=j;
min=count[i][j];
}
}
}
//------输出
if (min_i[counter]==0 && min_j[counter]==0 && flag[0][0]!=0)
{
output();
system("pause");
exit(1);
}
//--------迭代
if (count[min_i[counter]][min_j[counter]]>0)
{
for (i=0;i<9;i++)
if (c[min_i[counter]][min_j[counter]][i]==1)
run(min_i[counter],min_j[counter],i+1);
}
//--------回溯
counter--;
board[x][y]=0;
for (i=0;i<9;i++)
{
if (i!=x && flag[i][y]==0)
{
if (c[i][y][number-1]>1)
{
c[i][y][number-1]--;
if (c[i][y][number-1]==1)
count[i][y]++;
}
}
if (i!=y && flag[x][i]==0)
{
if (c[x][i][number-1]>1)
{
c[x][i][number-1]--;
if (c[x][i][number-1]==1)
count[x][i]++;
}
}
}
for (i=i1; i<i1 +3 ;i++)
for (j=j1;j<j1+3;j++)
if (i!=x && j!=y && flag[i][j]==0)
{
if (c[i][j][number-1]>1)
{
c[i][j][number-1]--;
if (c[i][j][number-1]==1)
count[i][j]++;
}
}
flag[x][y]=0;
}
void prenum()
{
int x1,y1,z1,a;
cout<<"请输入已知数字的个数"<<endl;
cin>>a;
cout<<"已知总数为"<<a<<",开始输入坐标及数值"<<endl;
cout<<"格式:x y z"<<endl;
cout<<"其中x为行,y为列,z为数值"<<endl;
for (int i=0;i<a;i++)
{
cin>>x1;
cin>>y1;
cin>>z1;
board[x1-1][y1-1]=z1;
cout<<"输入成功,坐标("<<x1<<","<<y1<<")处数字为"<<z1<<endl;
}
}
void precount()
{
for (int i=0;i<9;i++)
for (int j=0;j<9;j++)
{
if (!flag[i][j])
{
for (int k=1;k<=9;k++)
{
board[i][j]=k;
if (judge(i,j))
{
c[i][j][k-1]=1;
count[i][j]++;
}
}
board[i][j]=0;
}
}
}
int main()
{
int i0,j0;
prenum();
for (int i=0;i<9;i++)
for (int j=0;j<9;j++)
{
if (board[i][j]!=0)
flag[i][j]=1;
else
{
i0=i;
j0=j;
}
}
precount();
for (i=0;i<9;i++)
for (int j=0;j<9;j++)
{
if (flag[i][j]==0)
{
if (count[i][j]<count[i0][j0])
{
i0=i;
j0=j;
}
}
}
min_i[counter]=i0;
min_j[counter]=j0;
// cout<<i0<<" "<<j0<<endl;
for (int k=1;k<=9;k++)
if (c[min_i[counter]][min_j[counter]][k-1]==1)
run(min_i[counter],min_j[counter],k);
cout<<"no solution"<<endl;
return 0;
}
经测试,找到的所有的数独难题都能瞬间完成解答。