平时挺喜欢写小程序的,但是不知道写啥,偶然看到关于数独的新闻,觉得用小程序实现再合适不过了,网上一搜,有原理,有程序,但是没有找到优秀的代码,干脆自己写了一个,虽然也不优秀,起码自己看得懂。
原理:
1、每个格子可以是1-9的数,n行m列的数确定后,则第n行,第m列,nm所在的”宫“里其他的格子就不能是这个数了。
2、刚开始每个格子都有9个”候选数“,逐步把初始数据添加到最终的格子中,并把相应的格子中的候选数更新,如1所说。
3、开始计算,选出候选数最少的格子,对这些数迭代测试,如把候选数中的第1个添加到最终的格子中。和2一样更新其他格子的候选数列表。
4、3之后自然还是3,所以把3实现为递归要简单一些。而且考虑到本题实际情况最多递归81层,不会栈溢出。而且递归本身还保存了后续所需数据,简化了操作。
代码注释:
g_currentIndex:格子中已经确定结果的个数(添加到格子中的数的个数,递归的深度),索引表示的,所以0代表已添加1个。
g_affectedFlags:位标志,某次添加数据是否对格子产生影响,回退操作要使用。
g_candidate:每个格子的候选列表,用bitset的索引表示。第n位为1,代表n是候选数。
g_candidateNum:每个格子的候选个数,为什么不用bitset的count呢?因为速度太慢。
结果截图:
源码:
#include<iostream> #include<set> #include<bitset> #include<cstring> #include<time.h> using namespace std; int g_currentIndex=-1; bitset<81> g_affectedFlags[9][9]; bitset<10> g_candidate[9][9]; int g_candidateNum[9][9]; int resultNum; const int maxNum=5; int g_map[9][9]={ {8,0,0,0,0,0,0,0,0}, {0,0,3,6,0,0,0,0,0}, {0,7,0,0,9,0,2,0,0}, {0,5,0,0,0,7,0,0,0}, {0,0,0,0,4,5,7,0,0}, {0,0,0,1,0,0,0,3,0}, {0,0,1,0,0,0,0,6,8}, {0,0,8,5,0,0,0,1,0}, {0,9,0,0,0,0,4,0,0}, }; void AddElement(int row,int column,int num) { ++g_currentIndex; g_map[row][column]=num; int old; for(int i=0;i<9;++i) { if(g_map[row][i]==0 && g_candidate[row][i].test(num)) { g_candidate[row][i].reset(num); --g_candidateNum[row][i]; g_affectedFlags[row][i].set(g_currentIndex); } if(g_map[i][column]==0 && g_candidate[i][column].test(num)) { g_candidate[i][column].reset(num); --g_candidateNum[i][column]; g_affectedFlags[i][column].set(g_currentIndex); } } int palaceRow=row>2?(row>5?6:3):0; int palaceColumn=column>2?(column>5?6:3):0; for(int i=0;i<3;++i) { for(int j=0;j<3;++j) { row=palaceRow+i; column=palaceColumn+j; if(g_map[row][column]==0 && g_candidate[row][column].test(num)) { g_candidate[row][column].reset(num); --g_candidateNum[row][column]; g_affectedFlags[row][column].set(g_currentIndex); } } } } void RecoverElement(int row,int column,int num) { g_map[row][column]=0; for(int i=0;i<9;++i) { if(g_map[row][i]==0 && g_affectedFlags[row][i].test(g_currentIndex)) { g_candidate[row][i].set(num); ++g_candidateNum[row][i]; g_affectedFlags[row][i].reset(g_currentIndex); } if(g_map[i][column]==0 && g_affectedFlags[i][column].test(g_currentIndex)) { g_candidate[i][column].set(num); ++g_candidateNum[i][column]; g_affectedFlags[i][column].reset(g_currentIndex); } } int palaceRow=row>2?(row>5?6:3):0; int palaceColumn=column>2?(column>5?6:3):0; for(int i=0;i<3;++i) { for(int j=0;j<3;++j) { row=palaceRow+i; column=palaceColumn+j; if(g_map[row][column]==0 && g_affectedFlags[row][column].test(g_currentIndex)) { g_candidate[row][column].set(num); ++g_candidateNum[row][column]; g_affectedFlags[row][column].reset(g_currentIndex); } } } --g_currentIndex; } void Init() { for(int i=0;i<9;++i) { for(int j=0;j<9;++j) { g_candidate[i][j].set(); g_candidateNum[i][j]=10; } } for(int i=0;i<9;++i) { for(int j=0;j<9;++j) { if(g_map[i][j]!=0) AddElement(i,j,g_map[i][j]); } } } bool FindBest(int &row,int &column) { int min=999; for(int i=0;i<9;++i) { for(int j=0;j<9;++j) { if(g_map[i][j]==0 && g_candidateNum[i][j]>1 && g_candidateNum[i][j]<min) { row=i; column=j; min=g_candidateNum[i][j]; } } } if(min==999) return false; return true; } bool CheckResult() { set<int> elements; set<int> elements2; for(int i=0;i<9;++i) { for(int j=0;j<9;++j) { elements.insert(g_map[i][j]); elements2.insert(g_map[j][i]); } if(elements.size()!=9) return false; if(elements2.size()!=9) return false; elements.clear(); elements2.clear(); } elements.clear(); int row,column; for(int i=0;i<3;++i) { for(int j=0;j<3;++j) { row=i*3; column=j*3; for(int k=0;k<9;++k) { elements.insert(g_map[row+k/3][column+k%3]); } if(elements.size()!=9) return false; elements.clear(); } } return true; } void OutputResult() { cout<<endl; for(int i=0;i<9;++i) { for(int j=0;j<9;++j) { cout<<g_map[i][j]<<" "; } cout<<endl; } } bool Try() { int row,column; if(!FindBest(row,column)) return true; for(int i=1;i<10;++i) { if(!g_candidate[row][column].test(i)) continue; AddElement(row,column,i); if(Try()) { if(g_currentIndex==80 && CheckResult()) { cout<<endl<<"Result:"<<++resultNum<<endl; OutputResult(); if(resultNum>=maxNum) return false; } } else return false; RecoverElement(row,column,i); } return true; } int main() { double start,end,cost; start=clock(); Init(); Try(); if(resultNum) cout<<endl<<"OK!"<<endl; else cout<<endl<<"Wrong Input!"<<endl; end=clock(); cost=end-start; cout<<"Costed time:"<<cost<<"ms"<<endl; char c; cin>>c; return 0; }
其他:
个人觉得这种方法已经到了速度的极限,高手的代码竟然能够达到几十毫秒,表示自叹不如!
我用的例子是”史上最难数独“,确实只有一个解,代码中maxNum可以限制显示结果数量。
附其他高手的截图一张: