起因,在csdn的问答区,看到有个待回答的问题,题目“c语言数独程序缺少函数”,但是截止我发文,还没有人回答,我想分享下我的想法。
C语言版本
主要部分代码:
bool Check(char b[][COL],int n, char key){
int x,y,i,j;
x = n / 9;
y = n % 9;
// 检查纵坐标方向
for(i=0;i<ROW;i++)
if(b[i][y] == key && x!=i)
return false;
// 检查横坐标方向
for(j=0;j<COL;j++)
if(b[x][j] == key && y!=j)
return false;
// 检查小宫格
for(i = x/3*3 ; i < x/3*3+3 ; i ++) // 9宫格内
for(j = y/3*3 ; j < y/3*3+3 ; j++)
if(b[i][j]==key && (x != i && y != j))
return false;
return true;
}
bool DFS(char b[][COL], int n){
int x,y;
x = n / 9;
y = n % 9;
if(n>80)
return true;// 退出条件
if(b[x][y]!=' '){ //不为空
return DFS(b,n+1);
}else{
char c;
//遍历各个情况:枚举
for(int i=1;i<=9;i++){
c = i+'0';
if(Check(b, n, c)){
b[x][y] = c;
// 继续搜索
bool res = DFS(b,n+1);
if(res==true)
return res;
/* 如果构造不成功,还原当前位 */
b[x][y] = ' ';
}
}
}
return false;
}
完整代码:常规操作
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<time.h>
#include <assert.h>
#define ROW 9
#define COL 9
#define CORRECT 0
#define WRONG -1
#define MAXNUM 1000 /*数独游戏个数*/
#define bool int
#define true 1
#define false 0
bool DFS(char b[][COL], int n);
int checkSudoku(const char a[][COL]){
int i,j,x,y;
for( i = 0; i < ROW ; i++){
for(j = 0 ; j < COL ; j++){
for(x = 0 ; x < COL ; x++) // 列
if(a[i][x] == a[i][j] && x != j )
return WRONG;
for(x = 0 ; x < ROW ; x++) // 行
if(a[x][j] == a[i][j] && x != i )
return WRONG;
for(x = i/3*3 ; x < i/3*3+3 ; x ++) // 9宫格内
for(y = j/3*3 ; y < j/3*3+3 ; y++)
if(a[x][y] == a[i][j] && (x != i && y != j))
return WRONG;
}
}
return CORRECT;
}
void printSudoku(const char a[][COL]){
int i,j;
printf("\n ┌───────┬───────┬───────┐\n");
for( i = 0 ; i < ROW ; i++){
if(i != 0 && i%3 == 0) printf(" ├───────┼───────┼───────┤\n");
printf(" │");
for( j = 0 ; j < COL ; j++){
if(j != 0 && j%3 == 0) printf(" │");
if(a[i][j]!=' ')
printf(" %d",(a[i][j]-'0')); /*原来代码这里出问题,原来是 printf(" %d",a[i][j]);*/
else
printf(" %c",a[i][j]);
}
printf(" │\n");
}
printf(" └───────┴───────┴───────┘\n");
}
void readFromFile(char a[][COL], const char filename[]){
// To Be Solved
// 从文件filename中读取到的数独初盘存储到数组a[][COL]中
// if((fp = fopen(filename, "r"))==NULL){
// printf("Fail to open file!\n");
// return;
// }
FILE *fp = fopen(filename, "r");
assert(fp != NULL);
int i=0, j=0, n=0;
for( i = 0; i < ROW ; i++)
for( j = 0; j < COL ; j++){
fscanf(fp,"%d", &n);
a[i][j] = (char)('0'+n);
if(a[i][j]=='0')
a[i][j] = ' ';
}
fclose(fp);
}
void writeToFile(char a[][COL], const char filename[]){
// To Be Solved
// 数独初盘存储到数组a[][COL]中存储到文件filename
FILE *fp;
if((fp = fopen(filename, "w+") )==NULL){ /*打开文件写模式*/
printf("cannot open the file.\n"); /*判断文件是否正常打开*/
return;
}
int i,j;
for( i=0;i<ROW;i++){
for( j=0;j<COL;j++)
if(a[i][j] == ' ')
fputc('0',fp);
else
fputc(a[i][j],fp);
fputc('\n',fp);
}
fclose(fp);
}
void solveSudoku(const char a[][COL], char b[][COL]){
// To Be Solved
// 参数const char a[][COL]表示初盘二维数组;
// 参数char b[][COL]表示解的二维数组。
int i,j;
for(i=0;i<ROW;i++)
for(j=0;j<COL;j++)
b[i][j] = a[i][j];
DFS(b,0);
}
int main(){
char starting_grid[ROW][COL]={0};
char result[ROW][COL]={0};
char filename[50];
int rr;
double time_from, time_to, time_sum = 0;
int i;
for( i = 0; i < MAXNUM ; i++){
sprintf(filename,"shuduku\\%04d.txt",i);
readFromFile(starting_grid,filename);
printf("\n\n *%4d *",i);
printSudoku(starting_grid);
time_from = clock();
solveSudoku(starting_grid,result);
time_to = clock();
time_sum += time_to - time_from;
printSudoku(result);
rr = checkSudoku(result);
if(rr == WRONG){
printf("Something goes wrong...\n");
//return WRONG;
continue;
}
else
printf(" Correct!!!\n");
printf(" ==============================");
}
printf("\n Congretulations! ToTal Time:%fs\n",time_sum / CLOCKS_PER_SEC);
system("pause");
}
bool Check(char b[][COL],int n, char key){
int x,y,i,j;
x = n / 9;
y = n % 9;
// 检查纵坐标方向
for(i=0;i<ROW;i++)
if(b[i][y] == key && x!=i)
return false;
// 检查横坐标方向
for(j=0;j<COL;j++)
if(b[x][j] == key && y!=j)
return false;
// 检查小宫格
for(i = x/3*3 ; i < x/3*3+3 ; i ++) // 9宫格内
for(j = y/3*3 ; j < y/3*3+3 ; j++)
if(b[i][j]==key && (x != i && y != j))
return false;
return true;
}
bool DFS(char b[][COL], int n){
int x,y;
x = n / 9;
y = n % 9;
if(n>80)
return true;// 退出条件
if(b[x][y]!=' '){ //不为空
return DFS(b,n+1);
}else{
char c;
int i;
//遍历各个情况:枚举
for(i=1;i<=9;i++){
c = i+'0';
if(Check(b, n, c)){
b[x][y] = c;
// 继续搜索
bool res = DFS(b,n+1);
if(res==true)
return res;
/* 如果构造不成功,还原当前位 */
b[x][y] = ' ';
}
}
}
return false;
}
我只实现了关键部分(readFromFile、writeToFile、solveSudoku、Check、DFS),
其他部分效果(checkSudoku、printSudoku、main)是由提出问题的同学实现的
效果不是很快,在同样1000的数据量下,速度达到:
C++版本:(多了剪枝,速度提高)
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<time.h>
#include <assert.h>
#define ROW 9
#define COL 9
#define N 9
#define CORRECT 0
#define WRONG -1
#define MAXNUM 1 /*数独游戏个数*/
#define bool int
#define true 1
#define false 0
#define lowbit(x) ( x&(-x) )// 返回x二进制数的最小1出现的数
int ones[1<<N],map[1<<N];
// ones存储的是index对应的二进制中1个数,map存储index对应二进制数中1的位置
int col[N], row[N], cell[3][3];
// 行,列可用数字,记录9大块的已用情况
int get(int x, int y) {
return row[x] & col[y] & cell[x/3][y/3];
}
void init();
void new_game();
bool DFS(char b[][COL], int);
int checkSudoku(const char a[][COL]){
int i,j,x,y;
for( i = 0; i < ROW ; i++){
for(j = 0 ; j < COL ; j++){
for(x = 0 ; x < COL ; x++) // 列
if(a[i][x] == a[i][j] && x != j )
return WRONG;
for(x = 0 ; x < ROW ; x++) // 行
if(a[x][j] == a[i][j] && x != i )
return WRONG;
for(x = i/3*3 ; x < i/3*3+3 ; x ++) // 9宫格内
for(y = j/3*3 ; y < j/3*3+3 ; y++)
if(a[x][y] == a[i][j] && (x != i && y != j))
return WRONG;
}
}
return CORRECT;
}
void printSudoku(const char a[][COL]){
int i,j;
printf("\n ┌───────┬───────┬───────┐\n");
for( i = 0 ; i < ROW ; i++){
if(i != 0 && i%3 == 0) printf(" ├───────┼───────┼───────┤\n");
printf(" │");
for( j = 0 ; j < COL ; j++){
if(j != 0 && j%3 == 0) printf(" │");
if(a[i][j]!=' ')
printf(" %d",(a[i][j]-'0')); /*原来代码这里出问题,原来是 printf(" %d",a[i][j]);*/
else
printf(" %c",a[i][j]);
}
printf(" │\n");
}
printf(" └───────┴───────┴───────┘\n");
}
void readFromFile(char a[][COL], const char filename[]){
// To Be Solved
// 从文件filename中读取到的数独初盘存储到数组a[][COL]中
// if((fp = fopen(filename, "r"))==NULL){
// printf("Fail to open file!\n");
// return;
// }
FILE *fp = fopen(filename, "r");
assert(fp != NULL);
int i=0, j=0, n=0;
for( i = 0; i < ROW ; i++)
for( j = 0; j < COL ; j++){
fscanf(fp,"%d", &n);
a[i][j] = (char)('0'+n);
if(a[i][j]=='0')
a[i][j] = ' ';
}
fclose(fp);
}
void writeToFile(char a[][COL], const char filename[]){
// To Be Solved
// 数独初盘存储到数组a[][COL]中存储到文件filename
FILE *fp;
if((fp = fopen(filename, "w+") )==NULL){ /*打开文件写模式*/
printf("cannot open the file.\n"); /*判断文件是否正常打开*/
return;
}
int i,j;
for( i=0;i<ROW;i++){
for( j=0;j<COL;j++)
if(a[i][j] == ' ')
fputc('0',fp);
else
fputc(a[i][j],fp);
fputc('\n',fp);
}
fclose(fp);
}
void solveSudoku(const char a[][COL], char b[][COL]){
// To Be Solved
// 参数const char a[][COL]表示初盘二维数组;
// 参数char b[][COL]表示解的二维数组。
int i,j,t;
new_game();
int cnt = 0;// 空格数目
for(i=0;i<N;i++)
for(j=0;j<N;j++){
b[i][j] = a[i][j];
if(b[i][j]!=' '){
t = b[i][j] - '1';
row[i] -= 1<<t;
col[j] -= 1<<t;
cell[i/3][j/3] -= 1<<t;
}else
cnt++;
}
DFS(b,cnt);
}
深度搜索+剪枝 ///
// 存储列、行1的数量;整个棋盘完整
// 初始化ones、map一次就行
void init() {
int i,j;
for (i = 0; i < N; i++)
map[1 << i] = i;
for (i = 0; i < 1 << N; i++) {
int s = 0;
for (j = i; j; j -= lowbit(j))
s++;
ones[i] = s;// i的二进制表示中有s个1
}
}
void new_game() {
int i,j;
for ( i = 0; i < N; i++)
row[i] = col[i] = (1 << N) - 1; // 9位全置1的数字,初始化都能用
for (i = 0; i < 3; i++)
for ( j = 0; j < 3; j++)
cell[i][j] = (1 << N) - 1; // 9位全置1的数字,初始化都能用
}
bool DFS(char b[][COL],int cnt){
if(!cnt) return true;
int i,j,t,minn=10,x,y;
for(i=0;i<N;i++)
for(j=0;j<N;j++)
if(b[i][j]==' '){
t = ones[get(i,j)];
if(minn>t){
minn=t, x=i, y=j;
}
}
for(i=get(x,y);i;i-=lowbit(i)){
t = map[lowbit(i)];
row[x] -= 1<<t;
col[y] -= 1<<t;
cell[x/3][y/3] -= 1<<t;
b[x][y] = '1' + t;
if(DFS(b,cnt-1)) return true;
row[x] += 1<<t;
col[y] += 1<<t;
cell[x/3][y/3] += 1<<t;
b[x][y] = ' ';
}
return false;
}
/
int main(){
char starting_grid[ROW][COL]={0};
char result[ROW][COL]={0};
char filename[50];
int rr;
double time_from, time_to, time_sum = 0;
int i;
init();
for( i = 0; i < MAXNUM ; i++){
sprintf(filename,"C:\\Users\\Lenovo\\Desktop\\tmp\\shuduku\\%04d.txt",i);
readFromFile(starting_grid,filename);
printf("\n\n *%4d *",i);
printSudoku(starting_grid);
time_from = clock();
solveSudoku(starting_grid,result);
time_to = clock();
time_sum += time_to - time_from;
printSudoku(result);
rr = checkSudoku(result);
if(rr == WRONG){
printf("Something goes wrong...\n");
//return WRONG;
continue;
}
else
printf(" Correct!!!\n");
printf(" ==============================");
}
printf("\n Congretulations! ToTal Time:%fs\n",time_sum / CLOCKS_PER_SEC);
system("pause");
}
效果达到意外的好!!!
3. 参考资料
#include<iostream>
#include<algorithm>
using namespace std;
/*
题意:就是给你一个九宫格,让你填数,使横行包含1 ~ 9,纵行包含1 ~ 9,每一块包含1 ~ 9 。
思路:本题采用深搜,然后通过用col和row分别记录横行和纵行没有被用过的数,cell来标记没有被用过的数,
通过0代表这个数已经被用过了,1代表这个数没有被用过,
然后通过剪枝找到某个数可以填数的最小可能,这个最小可能通过row[x] & cell[x/3][y/3] & col[y]来找到
*/
const int N = 9;
// ones,map数组是为了方便对应数字转换后得到的数值
int ones[1<<N],map[1<<N];// ones:转换数字为1个数;map表示lowbit()返回值二进制中1的位置
int row[N], col[N],cell[3][3]; // 行,列可用数字,记录9大块的已用情况
char str[81];
//inline修饰符,表示为内联函数
inline int lowbit(int x){
// 取出n在二进制表示下最低位的1以及它后面的0构成的数值
return x & (-x);
}
inline int get(int x, int y) {
return row[x] & col[y] & cell[x/3][y/3];
}
void new_game() {
for (int i = 0; i < N; i++)
row[i] = col[i] = (1 << N) - 1; // 9位全置1的数字,初始化都能用
for (int i = 0; i < 3; i++)
for (int j = 0; j < 3; j++)
cell[i][j] = (1 << N) - 1; // 9位全置1的数字,初始化都能用
}
void init() {
for (int i = 0; i < N; i++)
map[1 << i] = i;
for (int i = 0; i < 1 << N; i++) {
int s = 0;
for (int j = i; j; j -= lowbit(j))
s++;
ones[i] = s;// i的二进制表示中有s个1
}
}
bool dfs(int);
int main() {
init();
while (cin >> str, str[0] != 'e') {
new_game();
int cnt=0;// 记录空格数目
for (int i = 0, k = 0; i < N; i++)
for (int j = 0; j < N; j++, k++)
if (str[k] != '.') {
int t = str[k] - '1';
row[i] -= 1 << t;
col[j] -= 1 << t;
cell[i / 3][j / 3] -= 1 << t;
}
else
cnt++;
dfs(cnt);
cout << str << endl;
}
}
bool dfs(int cnt) {//传入的是空位个数
if (!cnt) return true;
int x, y,minn=10,t;
// 寻找最小填数可能的空位的位置
for(int i=0;i<N;i++)
for(int j=0;j<N;j++)
if (str[i * N + j] == '.') {
t = ones[get(i, j)];// 可能性个数
if (minn > t) {
minn = t, x = i, y = j;
}
}
// 尝试各种可能
for (int i = get(x,y); i; i -= lowbit(i)) {
t = map[lowbit(i)];// 二进制中1的位置
row[x] -= 1 << t;
col[y] -= 1 << t;
cell[x / 3][y / 3] -= 1 << t;
str[x * N + y] = '1' + t;
if (dfs(cnt - 1)) return true;
// 失败了,恢复现场
row[x] += 1 << t;
col[y] += 1 << t;
cell[x / 3][y / 3] += 1 << t;
str[x * N + y] = '.';
}
return false;
}
/*
输入样例:
4.....8.5.3..........7......2.....6.....8.4......1.......6.3.7.5..2.....1.4......
......52..8.4......3...9...5.1...6..2..7........3.....6...1..........7.4.......3.
end
输出样例:
417369825632158947958724316825437169791586432346912758289643571573291684164875293
416837529982465371735129468571298643293746185864351297647913852359682714128574936
*/