基于DFS实现数独游戏解法

起因,在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. 参考资料

参考代码:AcWing 166. 数独 - AcWing

视频:AcWing 166. 数独 - AcWing

#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
*/

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

广大菜鸟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值