数独,应该不用我说明吧,是一个9*9的矩阵,矩阵里的每一个数字都是1~9中的一个。在每一行、每一列每个数字只能出现一次,另外,在每一个九宫格里每个数字也只能出现一次。
我曾经在网上看过一种“假”的数独生成法,生成的数独其实是有规律的,只要把中间的九宫生成好,那么周围的8个九宫就可以按照规律自动生成,但是这样的话便达不到“随机”的效果,于是经过苦想我想出了下面的方法:
按行生成数字。如果生成了与该行或该列相同的数字,或在这个九宫里有重复的数字,则删除刚刚生成的相同数字并且重新生成该数字。简而言之,就是先检查行,再检查列,再检查九宫,直到把这81个数字都生成出来。如果某一行出现“无解”的情况,则重新生成上一行,称之为“回溯”。
可想而知,这样的程序效率是不高的。首先的问题就是,要如何生成第一行没有重复的数字?最原始的办法就是从1~9依次随机,如果生成的数与该行某个数相同,则重新生成。但是这样的话程序效率很低,因为随机的次数会很多,比方说假设到了要生成某一行的最后一个数了,那么这个数成功生成的概率只有1/9,电脑平均要随机9次才可以生成。因此我想的方法是把每一行能够填入的数保存在一个数组avail里,并且初始化int avail[9]= {1,2,3,4,5,6,7,8,9},一旦成功生成某个数,那么就在avail里删除这个数,例如生成了3,那么就从avail里删掉‘3’,这样的话就可以大大的减少随机的次数。
按照这个思路,我设计了一下代码:
#include <iostream>
#include <stdlib.h>
#include <time.h>
#include <conio.h>
using namespace std;
int sudoku[9][9],avail[9]={1,2,3,4,5,6,7,8,9};//定义数独数组和可选数字数组
代码中的sudoku是用来保存数独的二维数组,avail是每一行可以填入的数字,可以知道,每当行数向下加一的时候avail就必须要重置一次。
void remove(int t[],int);//声明移除前置函数
void restore(void);//声明重置函数
void print(void);//声明打印数独函数
void init(void);//声明初始化函数
int search(int t[],int);//声明搜索下标函数
int en=9;//可选avail下标
Remove函数是用来删除t数组(也就是avail数组)里某个特定的数的,restore函数是用于重置avail数组的,print函数是用于打印数组的,init函数是用于初始化sudoku二维数组。Search函数用于查找avail数组中某个数的位置(下标),以便删除。如果这个数不存在则返回-1。整数en是可选数字的最大下标,每从avail里删除一个数,则都要有en–。
下面是函数的定义:其中srand是根据时间来随机化。
void init() //初始化
{
int x,y;
srand(time(NULL));
for (y=0;y<=8;y++)
{
for (x=0;x<=8;x++)
{
sudoku[y][x]=0;
}
}//初始化数组
}
void remove(int t[],int n) //n为下标,移除排除下标,项前移
{
int i;
for (i=n;i<=7;i++)
{
t[i]=t[i+1];
}
en–;//可选数字在原有基础上减
}
int search(int t[],int n) //n为搜索的数字,返回下标,如果没有则返回-1
{
int i;
for (i=0;i<=en-1;i++)
if (t[i]==n)
return i;
return -1;
}
void restore() //初始化en,avail
{
int i;
en=9;
for (i=0;i<=8;i++)
avail[i]=i+1;
}
void print(void)
{
int i,j;
for (i=0;i<=8;i++)
{
for (j=0;j<=8;j++)
{
cout << sudoku[i][j] << ” “;
}
cout << endl;
}
}
接下来就只有main函数了。
我的思路是每生成一个数,马上判断它的同行、同列和同九宫里有没有与之相同的数,如果有则重新生成该数,没有则从avail里删除这个数。判断同行和同列比较方便,用简单的循环就可以搞定,排除九宫则稍微复杂一点,它需要两个循环。假设有两个整型变量x和y,分别是sudoku数组的列坐标和行坐标,已知某数的是sudoku[y][x],由于在C++中两个整型数相除的结果还是整数,那么sudoku[(y/3)*3][(x/3)*3]就是该数所在的九宫的第一个数,然后进行循环便可以判断九宫了。如果是无解,则重新生成上一行。Main函数源代码如下:
int main()
{
int x=0,y=0;
int kill;//已存在的数的小标
init();
cout << “正在生成一个数独…” << endl;
for (y=0;y<=8;y++)
{
for (x=0;x<=8;x++)
{
/*思路:每将生成一个新数字的时候,重新生成avail数列*/
int i,j;
for (i=0;i<=x;i++)//排除横行
{
kill=search(avail,sudoku[y][i]);
if (kill!=-1)
remove(avail,kill);
}
for (i=0;i<=y;i++)//排除纵行
{
kill=search(avail,sudoku[i][x]);
if (kill!=-1)
remove(avail,kill);
}
for (i=0;i<=2;i++) //排除九宫
{
for (j=0;j<=2;j++)
{
kill=search(avail,sudoku[(y/3)*3+i][(x/3)*3+j]);
if (kill!=-1)
remove(avail,kill);
}
}
if (en==0 && y>0) //排除无解
{
y-=2;
break;
}
else if (en==0 && y==0)
{
init();
y=-1;
break;
}
sudoku[y][x]=avail[rand()%en];
restore();
}
restore();
}
print();//打印数独
getch();
return 0;
}
心得:尽管经过优化,这个程序运算速度仍然要靠人品。这是个纯计算问题,没有用到一些高深的知识。没有用到类,函数与函数之间的耦合性也非常的强。但是这还是需要掌握对数组的操作。该程序在VC6.0下编译通过。