admin 管理员组

文章数量: 1087840


2024年4月28日发(作者:抽象类的用处)

数独(sudoku)的生成与破解

一、 数独游戏的规则

数独(sudoku),起源于瑞士,于1970 年代由美国的一家数学逻辑游戏杂志首先发表,当时名

为Number Place。及后在日本大力推广下得以发扬光大,于1984 年取名“数独”,即“独立的数

字”的省略,在一个9×9的方格中,有81个小方格组成,然后又分9个大块,每块由3x3的方格

组成,就是中国的九宫阵。

游戏规则:“九宫阵”是一个9×9的方阵,它是由九个“九宫格”(图中黑色实线围住的3×3

的方阵)构成的,每个九宫格又是由九个小格子构成的,在每个小格子里面填上1~9中的数字,

使得每个数字在“九宫阵”的每行、每列、每个九宫格中均只出现一次。

游戏开始前会有一些格子上写好了数,你需要在剩下的格子里填数,真到把所有格子填满,并

且要求,任何一行或一列或者一个小九宫中没有相同的数字,当然你只能用1~9之间的9个数字。

如下图就是一个数独游戏。

题 目 答 案

二、 数独(sudoku)的生成

基本思路:

为了保证生成的数独一定有解,就要先得到了一个完整的数独(可以想象所有满足条件的组合

是很多的),然后从格子里面随机挖掉一些数字就可以了。那么如何得到一个完整数独呢?我们可

以这样想先给每一行(或列)填入一个不同的数,然后用“数独破解”的方法去完成,这就OK了。

步骤如下:

1.在第i行 (i=0~8)随机找个格子map[i][j] (j=0~8)填入本行数i+1;

2.用“数独求解”(见数独(sudoku)的生成)的方法产生一个完整数独;

3.从81个格子里面随机挖去n个数字。

三、 数独(sudoku)的破解

我们可以想象每个格子可填入的数字是可以得知的,但这不是唯一的,应先填入哪个数字就是

个问题,另外先填哪一个格子也是不确定的,那么如何开始填才最佳呢?

这里我们需要引入一个概念——格子的不确定度,一个格子可以填的数字的个数称为它的不确

定度。所以我们需要用递归下面的做法:

1.我们去寻找不确定度最小的格子;

2.确定这个格子可以填的数字;

3.填入一个数字后,递归。

随着填上去数字增多,剩余空格上的不确定度也会降低,如果某个格子上的不确定度降到1,

那这个格子可以先确定下来,如果降到了0,哦,非常遗憾,在前面的填数中一定是填错了,你不

得不回退。

四、 唯一解的问题

这里有一问题,我们需要的是我们生成的数独是否具有唯一解?是的,按上面数独生成的过程

是不能保证的。为了保证数独有唯一解,应该从哪儿考虑呢?

我的思路是:从完整数独中挖格子的过程中去分析。

根据数独破解的方法,在破解过程中每次要寻找的最小不确定度的那个格子,如果每次找到的

这个格子可填的数字都仅为1个(最小不确定度为1)时,那么得到的解一定唯一。这样就给我们

启发,我们在挖格子时,每挖一个格子,也去检测最小不确定度的格子的不确定度。如果这个不确

定度大于1,则就要重新这次挖格子。另外也有可能挖遍所有的格子(程序中我设的上限是81次)

也没有合适解时,那么要中止这次操作,这时就要从重新生成一个数独。

五、 游戏的难易程序

这个问题我是这么考虑的,只要生成的数独中的空格数(blank)越多,那么就越难。所以在

些程序中我设置三个级别:

1 容易: blank=33~35;

2 中等: blank=36~38;

3 困难: blank=39~41;

六、 源程序

#ifndef SUDOKU_RICK_0701_

#define SUDOKU_RICK_0701_

class CSudoku

{

int map[9][9];

int blanks;

int smod;

int solves;

int check(int,int,int*);

void dfs();

public:

enum{ANY=0,ALL=1};

CSudoku(int);

CSudoku::CSudoku(int *data);

void SudokuGenerator(int); // 随机生成数独,n越大越难

void SudokuGenerator(int *data); // 人工指定数独

//virtual ~CSudoku();

void display(); // 显示数独

int resolve(int mod=ALL); // 解数独

void analyze();

};

#endif

#include "stdio.h"

#include "stdlib.h"

#include "time.h"

#include "iostream"

#include "iomanip" //要用到格式控制符

using namespace std;

CSudoku::CSudoku(int n)

{

int j;

j=rand()%3;

blanks=n+j;

SudokuGenerator(blanks);

cout<

//cout<<(空格子数为"<

display();

cout<<"press enter to continue! "<

getchar();

getchar();

while(1)

{

cout<<" %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% "<

cout<<" %% 请选择您的操作: %% "<

cout<<" %%============================%% "<

cout<<" %% %% "<

cout<<" %% 1. 显示当前数独 %% "<

cout<<" %% 2. 分析求解 %% "<

cout<<" %% 3. 查看结果 %% "<

cout<<" %% 4. 返 回 %% "<

cout<<" %% %% "<

cout<<" %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% "<

int select;

cin>>select;

switch (select)

{

case 1:

{

cout<

display();

cout<<"press enter to continue! "<

getchar();

getchar();

break;

}

case 2:

{

analyze();

break;

}

case 3:

{

cout<

resolve();

cout<<"press enter to continue! "<

getchar();

getchar();

break;

}

case 4: return;

default:

{

cout<<"输入错误,请重新输入."<

break;

}

}

}

}

CSudoku::CSudoku(int *data)

{

SudokuGenerator(data);

cout<

display();

cout<<"press enter to continue! "<

getchar();

getchar();

analyze();

}

void CSudoku::SudokuGenerator(int n)

{

int i,j;

int mark[10];

srand(time(0));

//每一行i产生一个随机位置(列j)并置为当前行值i+1,0=

do

{

for(i=0;i<9;++i)

{

for(j=0;j<9;++j)

map[i][j]=0;

j=rand()%9;

map[i][j]=i+1;

}

//display();

}

while(!resolve(ANY)); //生成完整的随机Sudoku表格

// 挖窟窿

for(int k=0;k

{

int tmp,flag=0,sum=1;

do

{

// cout<<"sum="<

if (sum++>81)

{

SudokuGenerator(n);

return;

}

if (flag==1)

map[i][j]=tmp;

do

{

i=rand()%81;

j=i%9;

i=i/9;

}

while (map[i][j]==0);

tmp=map[i][j];

map[i][j]=0;

flag=1;

}

while (check(i,j,mark)>1);

}

}

void CSudoku::SudokuGenerator(int *data)

{

int *pm=(int*)map;

for(int i=0;i<81;++i)

pm[i]=data[i];

}

void CSudoku::display()

{

printf("┏━┯━┯━┳━┯━┯━┳━┯━┯━┓n");

for(int i=0;i<9;++i)

{

for(int j=0;j<9;++j)

{

if(map[i][j]>0)

{

if(j%3==0)

printf("┃ %d",map[i][j]);

else

printf("│ %d",map[i][j]);

}

else

{

if(j%3==0)

printf("┃ ");

else

printf("│ ");

}

}

printf("┃n");

if (i!=8)

{

if((i+1)%3==0)

printf("┣━┿━┿━╋━┿━┿━╋━┿━┿━┫n");

else

printf("┠─┼─┼─╂─┼─┼─╂─┼─┼─┨n");

} }

printf("┗━┷━┷━┻━┷━┷━┻━┷━┷━┛n");

}

int CSudoku::resolve(int mod)

{

smod=mod;

if(mod==ALL)

{

solves=0;

dfs();

return solves;

}

else if(mod==ANY)

{

try

{

dfs();

return 0;

}

catch(int)

{

return 1;

}

}

return 0;

}

int CSudoku::check(int y,int x,int *mark)

{

int i,j,is,js,count=0;

for(i=1;i<=9;++i)

mark[i]=0;

for(i=0;i<9;++i)

mark[map[y][i]]=1;

for(i=0;i<9;++i)

mark[map[i][x]]=1;

is=y/3*3;

js=x/3*3;

for(i=0;i<3;++i)

{

for(j=0;j<3;++j)

mark[map[is+i][js+j]]=1;

}

for(i=1;i<=9;++i)

if(mark[i]==0)

count++;

return count;

}

void CSudoku::dfs()

{

int i,j,im=-1,jm,min=10;

int mark[10];

// display();

//求自由度最小的格map[im][jm]

for(i=0;i<9;++i)

{

for(j=0;j<9;++j)

{

if(map[i][j]) //如果此格已填入数则看一下格。

continue;

int c=check(i,j,mark); //如果此格空,则求其自由度。

if(c==0) //已到结尾,没有空格了。

return;

if(c

{

im=i;

jm=j;

min=c;

}

}

}

if(im==-1) //若im=-1,则格子都填满。

{

if(smod==ALL) //smod==ALL是求解过程。

{

//printf("No. %d:n",++solves);

display();

return;

}

else if(smod==ANY) //smod==ANY是初始化过程。

{

throw(1);

}

}

check(im,jm,mark);

for(i=1;i<=9;++i)

{

if(mark[i]==0)

{

map[im][jm]=i; //从小到大让第一个可填的数填入自由度最小的格。

dfs();

}

}

map[im][jm]=0;

return;

}

void CSudoku::analyze()

{

while(1)

{

cout<<" %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% "<

cout<<" %% 请问你想做什么? %% "<

cout<<" %%============================%% "<

cout<<" %% %% "<

cout<<" %% 1. 查找最小不确定度的格子 %% "<

cout<<" %% 2. 指定格子的可填数 %% "<

cout<<" %% 3. 给指定格子填数 %% "<

cout<<" %% 4. 显示当前数独 %% "<

cout<<" %% 5. 查看结果 %% "<

cout<<" %% 6. 返 回 %% "<

cout<<" %% %% "<

cout<<" %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% "<

int select;

cin>>select;

switch(select)

{

case 1: //求不确定度最小的空格map[im][jm]

{

int i,j,im=-1,jm,min=10;

int mark[10];

for(i=0;i<9;++i)

{

for(j=0;j<9;++j)

{

if(map[i][j]) //如果此格已填入数则看一下格。

continue;

int c=check(i,j,mark); //如果此格空,则求其不确定度。

if(c==0)

{

cout<<"不能得到结果或已无空格子!自动返回! "<

return;

}

if(c

{

im=i;

jm=j;

min=c;

}

}

}

cout<

"<

cout<<"press enter to continue! "<

getchar();

getchar();

break;

}

case 2:

{

int x,y;

int mark[10];

cout< cin>>x>>y;

getchar();

while (map[x][y]!=0)

{

cout<

cout<<"请重新输入格子位置(如[2,4],则输入:2 4, 中间用空格.): [x,y]=";

cin>>x>>y;

getchar();

}

int i,j,is,js,count=0;

for(i=1;i<=9;++i)

mark[i]=0;

for(i=0;i<9;++i)

mark[map[x][i]]=1;

for(i=0;i<9;++i)

mark[map[i][y]]=1;

is=x/3*3;

js=y/3*3;

for(i=0;i<3;++i)

{

for(j=0;j<3;++j)

mark[map[is+i][js+j]]=1;

}

cout<

for(i=1;i<=9;++i)

if(mark[i]==0)

{

count++;

cout<

}

cout<

cout<<"press enter to continue! "<

getchar();

break;

}

case 3:

{

int x,y;

cout< cin>>x>>y;

cout<<"请输入要填入的数: ";

cin>>map[x][y];

cout<<"您填入的格为: map["<

cout<<"press enter to continue! "<

getchar();

getchar();

break;

}

case 4:

{

cout<

display();

cout<<"press enter to continue! "<

getchar();

getchar();

break;

}

case 5:

{

cout<

resolve(); //没有输入参数,则默认为smod==ALL,见程序开始函数声明。

cout<<"press enter to continue! "<

getchar();

getchar();

break;

}

case 6:

return;

default:

{

cout<<"输入错误,请重新输入."<

break;

}

}

}

}

void main()

{

int blanks;

while(1)

{

bool exit_f=true;

cout<

cout<<" %%%%%%%%%%%%%%%%%%%%%%%%% "<

cout<<" %% SUDOKU游戏 %% "<

cout<<" %% 任二艳制作 %% "<

cout<<" %%=====================%% "<

cout<<" %% 1. 新游戏 %% "<

cout<<" %% 2. 退 出 %% "<

cout<<" %%%%%%%%%%%%%%%%%%%%%%%%% "<

int select;

cin>>select;

switch (select)

{

case 1: //开始新游戏

{

while(exit_f)

{

cout<<" %%%%%%%%%%%%%%%%%%%%%%%%% "<

cout<<" %% 请选择游戏难度 %% "<

cout<<" %%=====================%% "<

cout<<" %% %% "<

cout<<" %% 1. 简 单 %% "<

cout<<" %% 2. 中 等 %% "<

cout<<" %% 3. 困 难 %% "<

cout<<" %% 4. 返 回 %% "<

cout<<" %% %% "<

cout<<" %%%%%%%%%%%%%%%%%%%%%%%%% "<

/*

cout<

cout<<"======================= "<

cout<<"1. 简 单"<

cout<<"2. 中 等"<

cout<<"3. 困 难"<

cout<<"4. 解一个已知数独 "<

cout<<"5. 退 出 "<

cout<<"======================= "<

int level;

cin>>level;

switch(level)

{

case 1:

{

blanks=33;

CSudoku s(blanks);

break;

}

case 2:

{

blanks=36;

CSudoku s(blanks);

break;

}

case 3:

{

blanks=39;

CSudoku s(blanks);

break;

}

case 4:

{

exit_f=false;

break;

}

default:

{

cout<<"输入错误,请重新输入."<

break;

}

}

}

break;

}

case 2: return;

default:

}

}

}

{

cout<<"输入有误,请重新选择!"<

break;

}


本文标签: 格子 数字 确定 输入 游戏