解数独算法的实现——剪枝优化

  最近人工智能做个小实验,组队选了个数独游戏,顺便研究了一下。解数独感觉主流思想也就是深搜回溯了吧,优化就是各种剪枝方法。

1 引言

  数独起源于18世纪初瑞士数学家欧拉等人研究的拉丁方阵(Latin Square),曾风靡日本和英国。现有解法包括基础解法:摒除法,余数法,进阶解法:区块摒除法(Locked Candidates)、数组法(Subset)、四角对角线(X-Wing)、唯一矩形(Unique Rectangle)、全双值坟墓(Bivalue Universal Grave)、单数链(X-Chain)、异数链(XY-Chain)及其他数链的高级技巧等等。已发展出来的方法有近百种之多。本解法中使用了余数法和数组法。

2 算法原理

  用个9*9的vector保存整个游戏,注意的是这里的9*9不是9行9列,而是9个九宫格,起初考虑时是希望能更简洁,不过最终都会用到位置所在的行数,列数和所在九宫格,似乎存行列只需要通过行列求9宫格更方便,而存9*九宫格要通过第几个九宫格和九宫格中第几个位置来求行列,更复杂(—_—)! 算是个坑,以后有同学在做的话,要注意了哦!

  算法的流程也很简单,上个流程图,

  首先,数据结构是9*9的游戏盘,然后为每个位置的可选数字维护一个集合(set),每次更新数字时会同时更新相关位置的集合,稍后会讲到。还有个3*9的集合,是每行每列每个九宫格的可选数字的集合。

  然后说说剪枝算法,前面说到用了余数法和数组法,要声明的一点是,这些解法都是人类的解法,即为人类如何选取数字而达到最快解出题目,不过我们计算机深搜是直接选取可选数字最少的位置,所以可能叫法有争议,在此就不深入讨论了。

(1)余数法

  每次选择数字后,都要删除对应位置的可选数,及同行同列同九宫格中不能再次选择相同的数字了,所以更新下面图片中灰色位置的集合,如果有位置的可选数字为空,则回溯。

(2)数组法

  或许有的时候更新后所有位置都有可选数字,但此时已经出现冲突,例如,一行中只有2个空位,而他们的可选数字相同,这时一定无解了,所以需要回溯。

为了检测,我们需要将更新过的位置所处的行列九宫格的集合全部更新,(灰色位置包括的行列九宫格,实际就是所有行列和5个九宫格),更新方法就是对该集合包含的所有位置的可选数+已选数求并集,得出集合大小小于9则产生了冲突,回溯。

3 代码

  1 #include <iostream>
  2 #include <vector>
  3 #include <set>
  4 #include <cstdlib>
  5 #include <ctime>
  6 #include <fstream>
  7
  8 using namespace std;
  9
 10
 11 class Sudoku{
 12 public:
 13     vector<vector<int> > numMap;//9*9 0
 14     vector<vector<set<int> > > availableNum;//row,col,nine 1~9
 15     vector<vector<set<int> > > everyNum;
 16
 17     Sudoku(vector<vector<int> > v){
 18         initMap(v);
 19     }
 20
 21     bool updateMap(int small,int big,int value){  //9*9 vector  everyNum[big][small] = value
 22         if(numMap[big][small]==0){          //更新游戏盘,删除相关位置中的value
 23             set<int> &row = availableNum[0][getRow(small,big)];
 24             set<int> &col = availableNum[1][getCol(small,big)];
 25             set<int> &nin = availableNum[2][big];
 26             set<int>::iterator rowIt = row.find(value);
 27             set<int>::iterator colIt = col.find(value);
 28             set<int>::iterator ninIt = nin.find(value);
 29             if(rowIt!=row.end()&&
 30                colIt!=col.end()&&
 31                ninIt!=nin.end()){
 32                 row.erase(rowIt);
 33                 col.erase(colIt);
 34                 nin.erase(ninIt);
 35                 numMap[big][small] = value;
 36                 set<int> s;
 37                 everyNum[big][small] =s;    //选中后集合中只有一个选中的数字本身
 38                 everyNum[big][small].insert(value);
 39                 if(updateEve(small,big,value))
 40                     return true;
 41                }
 42         }
 43         return false;
 44     }
 45     bool updateEve(int small,int big,int value){
 46         for(int i=0;i!=9;++i){
 47             if(numMap[big][i]==0){
 48                 set<int>::iterator it = everyNum[big][i].find(value); 49                 if(it!=everyNum[big][i].end()){ 50                     everyNum[big][i].erase(it); 51               } 52                 if(everyNum[big][i].size()==0)
 53                   return false; 54             }
 55         }
 56         int r = getRow(small,big);
 57         for(int j=0;j!=3;++j){
 58             for(int k=0;k!=3;++k){
 59                 int a=r/3*3+j;
 60                 int b=r%3*3+k;
 61                 if(numMap[a][b]==0){
 62                     set<int>::iterator it = everyNum[a][b].find(value); 63                 if(it!=everyNum[a][b].end()){ 64                     everyNum[a][b].erase(it); 65                 } 66                     if(everyNum[a][b].size()==0)
 67                       return false; 68  69             }
 70         }
 71         for(int j=0;j!=3;++j){
 72             for(int k=0;k!=3;++k){
 73                 int a=big%3+j*3;
 74                 int b=small%3+k*3;
 75                 if(numMap[a][b]==0){
 76                     set<int>::iterator it = everyNum[a][b].find(value); 77                 if(it!=everyNum[a][b].end()){ 78                     everyNum[a][b].erase(it); 79                 } 80                    if(everyNum[a][b].size()==0)
 81                     return false; 82  83             }
 84         }
 85         return true;
 86     }
 87     bool check(){    //数组法检查
 88         for(int i=0;i!=9;++i){
 89             set<int> s;
 90             for(int j=0;j!=9;++j){
 91                 for(auto it:everyNum[i][j]){
 92                     s.insert(it);
 93                 }
 94             }
 95             if(s.size()!=9)
 96                 return false;
 97         }
 98         for(int r=0;r!=9;++r){
 99             set<int> s;
100             for(int j=0;j!=3;++j){
101                 for(int k=0;k!=3;++k){
102                     int a=r/3*3+j;
103                     int b=r%3*3+k;
104                     for(auto it:everyNum[a][b]){
105                         s.insert(it);
106                     }
107                 }
108             }
109             if(s.size()!=9)
110                 return false;
111         }
112         for(int c=0;c!=9;++c){
113             set<int> s;
114             for(int j=0;j!=3;++j){
115                 for(int k=0;k!=3;++k){
116                     int a=j*3+c/3;
117                     int b=k*3+c%3;
118                     for(auto it:everyNum[a][b]){
119                         s.insert(it);
120                     }
121                 }
122             }
123             if(s.size()!=9)
124                 return false;
125         }
126         return true;
127     }
128     void initMap(vector<vector<int> > vv){
129         vector<int> v(9,0);
130         set<int> s;
131         for(int i=1;i!=10;++i){
132             s.insert(i);
133         }
134         vector<set<int> > sv(9,s);
135         availableNum = vector<vector<set<int> > >(3,sv);
136         everyNum = vector<vector<set<int> > >(9,sv);
137         numMap = vector<vector<int> >(9,v);
138         for(int i=0;i!=9;++i){
139             vector<int> tmp = vv[i];
140             for(int j=0;j!=3;++j){
141                 for(int k=0;k!=3;++k){
142                     numMap[i/3*3+j][i%3*3+k]=tmp[j*3+k];
143                     if(tmp[j*3+k]!=0){
144                         int value = tmp[j*3+k];
145                         set<int> a;
146                         everyNum[i/3*3+j][i%3*3+k] = a;
147                         set<int> &row = availableNum[0][getRow(i%3*3+k,i/3*3+j)];
148                         set<int> &col = availableNum[1][getCol(i%3*3+k,i/3*3+j)];
149                         set<int> &nin = availableNum[2][i/3*3+j];
150                         set<int>::iterator rowIt = row.find(value);
151                         set<int>::iterator colIt = col.find(value);
152                         set<int>::iterator ninIt = nin.find(value);
153                         row.erase(rowIt);
154                         col.erase(colIt);
155                         nin.erase(ninIt);
156                     }
157                 }
158             }
159         }
160         showMap();
161         set<int> tmp;
162         for(int i=0;i!=9;++i){
163             for(int j=0;j!=9;++j){
164                 if(numMap[i][j]==0){
165                     everyNum[i][j] = getEve(j,i);
166                 }
167                 else{
168                     everyNum[i][j] = tmp;
169                     everyNum[i][j].insert(numMap[i][j]);
170                 }
171
172             }
173         }
174         showEve();
175     }
176     set<int> getEve(int small,int big){
177         set<int> &row = availableNum[0][getRow(small,big)];
178         set<int> &col = availableNum[1][getCol(small,big)];
179         set<int> &nin = availableNum[2][big];
180         set<int> res;
181         for(auto it:row){
182             if(col.find(it)!=col.end()&&nin.find(it)!=nin.end()){
183                 res.insert(it);
184             }
185         }
186         return res;
187     }
188
189     pair<int,int> getMin(){
190         pair<int,int> res = make_pair(9,9);
191         int Min =10;
192         for(int i=0;i!=9;++i){
193             for(int j=0;j!=9;++j){
194                 if(numMap[i][j]==0&&everyNum[i][j].size()<Min){
195                     res = make_pair(i,j);
196                     Min = everyNum[i][j].size();
197                 }
198             }
199         }
200         return res;
201     }
202     int getEveNum(int small,int big){
203         set<int> &row = availableNum[0][getRow(small,big)];
204         set<int> &col = availableNum[1][getCol(small,big)];
205         set<int> &nin = availableNum[2][big];
206         int res=0;
207         for(auto it:row){
208             if(col.find(it)!=col.end()&&nin.find(it)!=nin.end()){
209                 res++;
210             }
211         }
212         return res;
213     }
214     int getRow(int small,int big){
215         return big/3*3+small/3;
216     }
217     int getCol(int small,int big){
218         return big%3*3+small%3;
219     }
220     void showMap(){
221         for(int i=0;i!=9;++i){
222             cout<<"                              ";
223             if(i%3==0){
224                 cout<<"---------------------"<<endl;
225                             cout<<"                              ";
226             }
227             for(int j=0;j!=3;++j){
228                 cout<<"|";
229                 for(int k=0;k!=3;++k){
230                     if(numMap[i/3*3+j][i%3*3+k]==0)
231                         cout<<"  ";
232                     else
233                         cout<<numMap[i/3*3+j][i%3*3+k]<<" ";
234                 }
235             }
236             cout<<"|"<<endl;
237         }
238         cout<<"                              ";
239         cout<<"---------------------"<<endl;
240
241     }
242     void showEve(){
243         for(int i=0;i!=9;++i){
244             cout<<"                              ";
245             if(i%3==0){
246                 cout<<"---------------------"<<endl;
247                             cout<<"                              ";
248             }
249             for(int j=0;j!=3;++j){
250                 cout<<"|";
251                 for(int k=0;k!=3;++k){
252                     if(numMap[i/3*3+j][i%3*3+k]==0){
253                         for(auto it:everyNum[i/3*3+j][i%3*3+k])
254                             cout<<it;
255                         cout<<" ";
256                     }
257                     else
258                         cout<<numMap[i/3*3+j][i%3*3+k]<<" ";
259                 }
260             }
261             cout<<"|"<<endl;
262         }
263         cout<<"                              ";
264         cout<<"---------------------"<<endl;
265
266     }
267 };
268
269 bool solu(Sudoku mSudoku,int small,int big,int value);
270 bool solu(Sudoku mSudoku);
271 bool solu1(Sudoku mSudoku,int small,int big,int value);
272 bool solu1(Sudoku mSudoku){
273     pair<int,int> p = mSudoku.getMin();
274     for(auto i:mSudoku.everyNum[p.first][p.second]){
275         if(solu1(mSudoku,p.second,p.first,i))
276             return true;
277     }
278     return false;
279 }
280 bool solu(Sudoku mSudoku){
281     pair<int,int> p = mSudoku.getMin();
282     for(auto i:mSudoku.everyNum[p.first][p.second]){
283         if(solu(mSudoku,p.second,p.first,i))
284             return true;
285     }
286     return false;
287 }
288
289 bool solu(Sudoku mSudoku,int small,int big,int value){
290     if(!mSudoku.updateMap(small,big,value))
291         return false;
292     pair<int,int> p = mSudoku.getMin();
293     if(p==make_pair(9,9)){
294         mSudoku.showMap();
295         return true;
296     }
297     for(auto i:mSudoku.everyNum[p.first][p.second]){
298         if(solu(mSudoku,p.second,p.first,i))
299             return true;
300     }
301     return false;
302 }
303
304 bool solu1(Sudoku mSudoku,int small,int big,int value){
305     if(!mSudoku.updateMap(small,big,value)||!mSudoku.check())
306         return false;
307     pair<int,int> p = mSudoku.getMin();
308     if(p==make_pair(9,9)){
309         mSudoku.showMap();
310         return true;
311     }
312     for(auto i:mSudoku.everyNum[p.first][p.second]){
313         if(solu1(mSudoku,p.second,p.first,i))
314             return true;
315     }
316     return false;
317 }
318
 1 int main()
 2 {
 3     vector<int> a(9,0);
 4     vector<vector<int> > b(9,a);
 5     vector<vector<vector<int> > > v(95,b);
 6     vector<double> tim(95,0);
 7     vector<double> tim1(95,0);
 8     string str;
 9     for(int i=0;i!=95;++i){
10         cin>>str;
11         for(int j=0;j!=9;++j){
12             for(int k=0;k!=9;++k){
13                 if(str[j*9+k]==‘.‘)
14                     v[i][j][k]=0;
15                 else
16                     v[i][j][k]=str[j*9+k]-‘0‘;
17             }
18         }
19         Sudoku mSudoku(v[i]);
20         //unsigned long start = ::GetTickCount();
21         //cout<<start;
22         clock_t start = clock();
23         solu(mSudoku);
24         clock_t en   = clock();
25         tim[i] = (double)(en - start) / CLOCKS_PER_SEC;
26         start = clock();
27         solu1(mSudoku);
28         en   = clock();
29         tim1[i] = (double)(en - start) / CLOCKS_PER_SEC;
30     }
31     ofstream file("spendTime.txt");
32     for(int i=0;i!=95;++i){
33         file<<tim[i]<<",";
34     }
35     file<<endl;
36     file.close();
37     ofstream file1("spendTime1.txt");
38     for(int i=0;i!=95;++i){
39         file1<<tim1[i]<<",";
40     }
41     file1.close();
42     return 0;
43 }

写了个main()从测试从网上下载下来的95个用例,记录个时间,用python画出来。

可以看到对于一些回溯次数较多的用例,剪枝效果还是很不错的。

附:代码及相关文件

时间: 2024-08-08 11:44:24

解数独算法的实现——剪枝优化的相关文章

超高速指数模糊算法的实现和优化(10000*10000在100ms左右实现)。

今天我们来花点时间再次谈谈一个模糊算法,一个超级简单但是又超级牛逼的算法,无论在效果上还是速度上都可以和Boxblur, stackblur或者是Gaussblur想媲美,效果上,比Boxblur来的更平滑,和Gaussblur相似,速度上,经过我的优化,在PC端比他们三个都要快一大截,而且基本不需占用额外的内存,实在是一个绝好的算法. 算法的核心并不是我想到或者发明的,是一个朋友在github上挖掘到的,率属于Cairo这个2D图形库的开源代码,详见: https://github.com/r

基于暗通道去雾算法的实现与优化(二)opencv在pc上的实现

上一篇中,学习了何的论文中的去雾方法,这一篇中,我按照何的论文思路借助opencv 2.4.10 进行了实现,效果的确很好,就是耗时太多了,效果见下图:蓝色圆圈代表大气光值的取值点. 突然发现上一篇中忘了介绍大气光值A的求解了,论文中是这样做的: 1.首先取暗通道图中最亮的千分之一的像素点. 2.根据这些像素点的位置在原图中搜索一个最亮的点,这个点的强度(intensity)就是我们要求的A啦. 论文作者何认为这样做的好处就是避免了原图中比较亮的物体作为A的值,比如图片中的白色的汽车,如果从原图

跳舞链解数独 静态数组优化

前几天有人问我之前写的那个跳舞链解数独的程序的内存泄漏问题如何解决,因此回顾了一下我的那个程序.现在看来那个程序简直不忍直视,于是大刀阔斧的改了.主要是把动态内存分配都改为了静态预分配,这样就可以避免频繁的调用malloc和free.同时静态分配的好处就是内存访问局部性比较好,cache不容易miss.而且在一行四个节点连续分配的情况下,就没有必要存储左右指针了.而且在连续分配的时候,指针都可以蜕变为数组索引,访问就比较简单了.还有一个好处就是整个程序可读性大大增强.现在这个版本的代码如下,利用

【原创】一个基于简单剪枝的DFS解数独程序

问题来源:leetCode Sudoku Solver Write a program to solve aSudoku puzzle by filling the empty cells. Empty cells are indicated by the character *.*. You may assume that there will be only one unique solution. 问题链接: https://oj.leetcode.com/problems/sudoku-

解数独——命令行程序的实现

Github项目地址 地址 PSP表格 PSP2.1 Personal Software Process Stages 预估耗时(min) 实际耗时(min) Planning 计划 30 15 Estimate 估计这个任务需要多少时间 48*60 12*60 Development 开发 20*60 10*60 Analysis 需求分析 (包括学习新技术) 2*60 5*60 Design Spec 生成设计文档 20 15 Design Review 设计复审 10 10 Coding

poj 1054 The Troublesome Frog (暴力搜索 + 剪枝优化)

题目链接 看到分类里是dp,结果想了半天,也没想出来,搜了一下题解,全是暴力! 不过剪枝很重要,下面我的代码 266ms. 题意: 在一个矩阵方格里面,青蛙在里面跳,但是青蛙每一步都是等长的跳, 从一个边界外,跳到了另一边的边界外,每跳一次对那个点进行标记. 现在给你很多青蛙跳过后的所标记的所有点,那请你从这些点里面找出 一条可能的路径里面出现过的标记点最多. 分析:先排序(目的是方便剪枝,break),然后枚举两个点,这两个 点代表这条路径的起始的两个点.然后是三个剪枝,下面有. 开始遍历时,

探讨排序算法的实现

排序算法是我们工作中使用最普遍的算法,常见的语言库中基本都会有排序算法的实现,比如c标准库的qsort,stl的sort函数等.本文首先介绍直接插入排序,归并排序,堆排序,快速排序和基数排序等比较排序算法,然后介绍计数排序,基数排序等具有线性时间的排序算法.本文主要讨论算法的实现方法,并不会过多介绍基本理论. 评价一个排序算法优劣适用与否,一般需要从三个方面来分析 时间复杂度.用比较操作和移动操作数的最高次项表示,由于在实际应用中最在乎的是运行时间的上限,所以一般取输入最坏情况的下的运行时间作为

Bug2算法的实现(RobotBASIC环境中仿真)

移动机器人智能的一个重要标志就是自主导航,而实现机器人自主导航有个基本要求--避障.之前简单介绍过Bug避障算法,但仅仅了解大致理论而不亲自动手实现一遍很难有深刻的印象,只能说似懂非懂.我不是天才,不能看几遍就理解理论中的奥妙,只能在别人大谈XX理论XX算法的时候,自己一个人苦逼的面对错误的程序问为什么... 下面开始动手来实现一下简单的Bug2避障算法.由于算法中涉及到机器人与外界环境的交互,因此需要选择一个仿真软件.常用的移动机器人仿真软件主要有Gazebo.V-rep.Webots.MRD

STL简单 copy 算法的实现

1.简介 不论是对客户端或对STL内部而言,copy() 都是一个常常被调用的函数.由于copy进行的是复制操作,而复制操作不外乎运用赋值运算符(assignment operator)或复制构造函数(copy constructor),但是某些元素的类型是trivial assignment operator,因此如果能使用内存直接进行复制(例如使用C标准函数memmove.memcpy),便能节约大量时间.为此,copy算法用尽各种办法,包括函数重载(function overloading