前天在我的小pad上装了个数独游戏,完了几把后兴趣索然了。不过突然想起来一直想写个解数独的程序,不过因为懒和拖拉,就一直没写。今天花了30分钟写了个解数独的程序,贴代码:
#include <cstdio>
#include <string>
#include <vector>
//某个数字填入后,需要检查的index
void get_affected_index(const int has_filled_index, int * affected_index) {
int row_number = has_filled_index / 9;
int column_number = has_filled_index % 9;
int affect_index_idx = 0;
for(int i = 0; i < 9 ; ++i){
if(i != row_number)
affected_index[affect_index_idx++] = i*9 + column_number;
}
for(int i = 0; i < 9; ++i){
if(i != column_number)
affected_index[affect_index_idx++] = row_number * 9 + i;
}
int vertical_index = row_number % 3;
int horizonal_index = column_number % 3;
int first_row =0, second_row = 0;
int first_column=0, second_column =0;
if (vertical_index == 0){
first_row = row_number + 1;
second_row = row_number + 2;
} else if (vertical_index == 1) {
first_row = row_number - 1;
second_row = row_number + 1;
} else if (vertical_index == 2) {
first_row = row_number - 2;
second_row = row_number - 1;
}
if(horizonal_index == 0) {
first_column = column_number + 1;
second_column = column_number + 2;
} else if (horizonal_index == 1) {
first_column = column_number - 1;
second_column = column_number + 1;
} else if (horizonal_index == 2) {
first_column = column_number - 2;
second_column = column_number - 1;
}
affected_index[affect_index_idx++] = first_row * 9 + first_column;
affected_index[affect_index_idx++] = first_row * 9 + second_column;
affected_index[affect_index_idx++] = second_row * 9 + first_column;
affected_index[affect_index_idx++] = second_row * 9 + second_column;
}
//填入一个数字后检查是否能够满足数独需求
bool check_status(char * shudu, const int has_filled_index) {
int affected_index[20] = {0};
get_affected_index(has_filled_index, affected_index);
for (int i=0; i<20; i++) {
int current_index = affected_index[i];
if(shudu[current_index] == shudu[has_filled_index])
return false;
}
return true;
}
//回溯法求解数独
bool solve(char * shudu, const int * blank_index, const int blank_num) {
int current_blank_num = 0;
while(current_blank_num < blank_num) {
if(current_blank_num<0){
return false;
}
int current_blank_index = blank_index[current_blank_num];
shudu[current_blank_index]++;
if (shudu[current_blank_index] == '9'+1){ //回溯:最后一个填入的数字清零并回退一格
shudu[current_blank_index] = '0';
current_blank_num--;
}
if (check_status(shudu, current_blank_index)) { //刚填的数字合法
current_blank_num ++;
continue;
} else { //刚填的数字不合法
continue;
}
}
return true;
}
//数独输出
void print_shudu(const char * shudu) {
for(int i=0; i<9; i++) {
for(int j=0; j<9; j++)
printf("%c ", shudu[ i*9 + j ]);
printf("\n");
}
}
//初始化数独:读取输入并计算多少个空位,纪录空位的index
void init_blank_index(const char * shudu, int * blank_index, int * blank_num){
int index = 0;
for(int i=0; i<81; i++) {
if(shudu[i] == '0'){
blank_index[index++] = i;
}
}
*blank_num = index;
return;
}
int main(int argc, char ** argv) {
char shudu[81] = {0};
int blank_index[81] = {0};
int blank_num = 0;
printf("Please Input the shudu sequence, no blank character needed:\n");
scanf("%s",shudu); //不做任何合法性检测
init_blank_index(shudu, blank_index, &blank_num);
if(solve(shudu, blank_index, blank_num)){
print_shudu(shudu);
}
return 0;
}
代码居然写了116行,比较扯。大部分代码是输入输出,检查填入数字合法性的部分写的太拖沓了。
往深了想一下,这个算法可以在哪儿进行优化呢?一个启发式的想法是:在初始化的时候就检查每个位置可能出现的数字,把可能出现的数字较少的位置放在前面先填。这也是我们玩数独的时候经常使用的策略——如果一个位置只可能有一个数字出现,那么就先把他填上,作为一个已知的条件,然后再继续寻找下一个……直到找不到这样的位置了,再进行试探。这样的话,外层循环的次数就大量减少,省去了很多无用的试探。
尽管这个想法或许真的能降低时间复杂度的,但缺少数学证明。不知道哪位大牛能够给出这个时间复杂度的证明?