比这篇新的文章: Codee#1739
比这篇旧的文章: Codee#1737

目前最快的数独求解程序 - 实现了Knuth的Dancing Links+Algorithm X算法

语言: C++, 标签: 数独 求解 2009/06/02发布 2年前更新 更新记录
作者: 半瓶墨水, 点击3826次, 评论(26), 收藏者(1), , 打分:

背景
主题: 字体:
001 //from: http://code.google.com/p/klsudoku/source/checkout
002 //半瓶墨水修改于 2009 Sept 18
003 //References
004 //  http://en.wikipedia.org/wiki/Knuth's_Algorithm_X
005 //  http://en.wikipedia.org/wiki/Dancing_Links
006 //  http://en.wikipedia.org/wiki/Exact_cover#Sudoku
007
008 #include<iostream>
009 using namespace std;
010 #define RR 729
011 #define CC 324
012 #define INF 1000000000
013 int mem1[81+1];
014 int mem2[81+1];
015 int *mem = mem1;
016 char ch[81+1];
017 int cnt[CC+1];
018 int scnt=0; //solution count
019
020 struct node {
021     int r,c;
022     node *up;
023     node *down;
024     node *left;
025     node *right;
026 }head,all[RR*CC+99],row[RR],col[CC];int all_t;
027
028 inline void link(int r,int c) {
029     cnt[c]++;
030     node *t=&all[all_t++];
031     t->r=r;
032     t->c=c;
033     t->left=&row[r];
034     t->right=row[r].right;
035     t->left->right=t;
036     t->right->left=t;
037     t->up=&col[c];
038     t->down=col[c].down;
039     t->up->down=t;
040     t->down->up=t;
041 }
042
043 inline void remove(int c) {
044     node *t,*tt;
045     col[c].right->left=col[c].left;
046     col[c].left->right=col[c].right;
047     for(t=col[c].down;t!=&col[c];t=t->down) {
048         for(tt=t->right;tt!=t;tt=tt->right) {
049             cnt[tt->c]--;
050             tt->up->down=tt->down;
051             tt->down->up=tt->up;
052         }
053         t->left->right=t->right;
054         t->right->left=t->left;
055     }
056 }
057
058 inline void resume(int c) {
059     node *t,*tt;
060     for(t=col[c].down;t!=&col[c];t=t->down) {       
061         t->right->left=t;
062         t->left->right=t;
063         for(tt=t->left;tt!=t;tt=tt->left) {
064             cnt[tt->c]++;
065             tt->down->up=tt;
066             tt->up->down=tt;
067         }
068     }   
069     col[c].left->right=&col[c];
070     col[c].right->left=&col[c];
071 }
072
073 void print_solution(int*m){
074   int ans[81];
075   for(int i=1;i<=81;i++) {
076     int t=m[i]/9%81;
077     int val=m[i]%9+1;
078     ans[t]=val;
079   }
080   for(int i=0;i<81;i++)
081     printf("%d",ans[i]);
082   printf("\n");
083 }
084
085 void solve(int k)
086 {
087     if(head.right==&head) {//got one solution
088       if (!scnt) {
089         memcpy(mem2, mem1, sizeof(mem1));
090         mem = mem2;
091       }
092       scnt++;
093       return;
094     }
095     node*t,*tt;
096     int min=INF,tc;
097     for(t=head.right;t!=&head;t=t->right) {
098         if(cnt[t->c]<min) {
099             min=cnt[t->c];
100             tc=t->c;
101             if(min<=1)break;
102         }
103     }
104     remove(tc);
105     for(t=col[tc].down;t!=&col[tc];t=t->down) {
106         mem[k]=t->r;
107         t->left->right=t;
108         for(tt=t->right;tt!=t;tt=tt->right) {
109             remove(tt->c);
110         }
111         t->left->right=t->right;
112         solve(k+1);
113         if (scnt >= 2)
114           return;
115         t->right->left=t;
116         for(tt=t->left;tt!=t;tt=tt->left) {
117             resume(tt->c);
118         }
119         t->right->left=t->left;
120     }
121     resume(tc);
122     return;
123 }
124
125 int main(int argc, char**argv) {
126   if (argc != 2) return 1;
127   const char *p = argv[1];
128   size_t len = strlen(p);
129   if (len!=81) return 1;
130   int i,j,k;
131   for (i=0;i<81;i++) {
132     if ((*p) == '0')
133       ch[i] = '.';
134     else
135       ch[i] = *p;
136     p++;
137   }
138   ch[81] = '\0';
139
140   if(ch[0]=='e')return 1;
141   all_t=1;
142   memset(cnt,0,sizeof(cnt));
143   head.left=&head;
144   head.right=&head;
145   head.up=&head;
146   head.down=&head;
147   head.r=RR;
148   head.c=CC;
149   for(i=0;i<CC;i++) {
150     col[i].c=i;
151     col[i].r=RR;
152     col[i].up=&col[i];
153     col[i].down=&col[i];
154     col[i].left=&head;
155     col[i].right=head.right;
156     col[i].left->right=&col[i];
157     col[i].right->left=&col[i];
158   }
159   for(i=0;i<RR;i++) {
160     row[i].r=i;
161     row[i].c=CC;
162     row[i].left=&row[i];
163     row[i].right=&row[i];
164     row[i].up=&head;
165     row[i].down=head.down;
166     row[i].up->down=&row[i];
167     row[i].down->up=&row[i];
168   }
169   for(i=0;i<RR;i++) {
170     int r=i/9/9%9;
171     int c=i/9%9;
172     int val=i%9+1;
173     if(ch[r*9+c]=='.'||ch[r*9+c]==val+'0') {
174       link(i,r*9+val-1);
175       link(i,81+c*9+val-1);
176       int tr=r/3;
177       int tc=c/3;
178       link(i,162+(tr*3+tc)*9+val-1);
179       link(i,243+r*9+c);
180     }
181   }
182   for(i=0;i<RR;i++) {
183     row[i].left->right=row[i].right;
184     row[i].right->left=row[i].left;
185   }
186   solve(1);
187   printf("\n");
188   if (scnt>1){
189     print_solution(mem1);
190     print_solution(mem2);
191     printf("\n2 or mroe solutions found\n");
192   } else if (scnt==1) {
193     print_solution(mem1);
194     printf("\none solution found\n");
195   } else {
196     printf("\nNO solution\n");
197   }
198 }


所有评论,共26条:( 我也来说两句)

1
winxos 2年前 回复
1
1
竟然这么短就实现了,强!
2
0
1
注:发现该算法的一个bug - 求解下面这个布局的时候结果不正确

求解下面这个布局:

.3.......
.1.2...6.
...6..4..
..4...8..
..7......
.....1..3
...46...5
...8.....
.5......1

得到这个结果:

436198572
71925436.
.85639417
194327856
327586194
568941273
271463985
946815732
853792641

结果中间居然出现了两个未知数

到底是什么问题。。。这个。。。有时间再说了...
3
0
3
@半瓶墨水: 终于知道是啥问题了,scnt+=solve的地方返回就行了,或者改巴改巴也可以打出所有解
4
0
2
已经改好,现在可以显示多解、无解、唯一解,并且加上了参考链接
5
snakech 11个月前 回复
0
0
高人啊,我对你这个dancing link很感兴趣啊,但是本人技术不好,尝试改出输出所有解,但是不成
您可以给我一份你改好的代码么,谢谢了
6
半瓶墨水 11个月前 回复
0
0
@snakech: 应该是已经改好了啊,你试试看
7
njuaplusplus 3个月前 回复
0
0
楼主 您好!
看了你的代码后,深受感触!
不过当中有一些不懂的地方!还望指教!

第026行: all[RR*CC+99]      请问这个为什么要加99,而且这个数组的作用是什么?
第089行前后: 为什么吧mem1复制给mem2,然后将mem指向mem2,这个用意是什么?
第107,111, 115, 119行:这几步操作看的不是很懂额 能不能解释一下!

谢谢!
非常感谢!
8
半瓶墨水 3个月前 回复
0
0
@njuaplusplus:
99是个约数,精确够用的话应该比这个小,但是作者也懒得计算了,就放了这么个数
指向mem2是因为求出一个解之后不能确定是不是唯一解,所以就继续求解了,求出第二个解的话就说明这个数独题目有问题
关于107-119那几行,直接去看wikipedia上面关于dancing links的那篇,一一对应就行了
9
njuaplusplus 3个月前 回复
0
0
@半瓶墨水:
首先,非常感谢您这么快回复!
      关于99这个问题,我觉得如果用链表实现的话,是用不了RR*CC这么多的,因为对于exact cover 的 数独链表中的“1”,应该小于等于4*RR的,所以想不通为什么这么实现,先开个这么大的数组,然后再利用all_t来指示,我觉得这个有些奇怪,不知道您有何想法?
      然后关于那个mem1复制的问题,我觉得不用复制,直接将mem只想mem2就行了哇
      再是关于107~109那几行,我理解了!太精妙了!

祝新年快乐啊!
10
半瓶墨水 3个月前 回复
0
0
@njuaplusplus: 具体数字你可以再研究研究。还有,你说的对,mem1不用复制
11
lazycat007 2个月前 回复
0
0
你好,算法看了一部分,基本上看明白了,不过有个小问题想问一下:

在函数resume中,第63行的代码:for(tt=t->left;tt!=t;tt=tt->left) {
这里是用来遍历要插入的列节点对应的行,是不是使用left和right都可以呢?还是说必须用left来遍历?
也许我能从其它的地方找到答案。

算法还在继续研究中,等完全研究完毕了,再发上来相关的注释。
12
lazycat007 1个月前 回复
0
0
算法全部研究完毕,这个算法如果想解决杀手数独还是力有不逮,需要进行改进,暂时没有思路。
13
njuaplusplus 1个月前 回复
0
0
@lazycat007: 不叫这个算法,应该是这个程序是解不了杀手数独的,如果按照算法来说的话,加入相关限制,应该是可以解杀手数独的,而且复杂度应该和解单纯的数独在一个数量级上的
14
njuaplusplus 1个月前 回复
0
0
@半瓶墨水: 这个程序存在致命的错误。。。可能会永远计算不出结果。。。
15
半瓶墨水 1个月前 回复
0
0
@njuaplusplus: 你能说说是哪个开局吗?我用这个程序计算过网上十万多个开局,都很好的呀
16
lazycat007 1个月前 回复
0
0
这个算法可以解决杀手数独,只要把行的内容修改了,就可以实现杀手数独的解题,而且速度很快。
17
lazycat007 1个月前 回复
0
0
@njuaplusplus: 我知道你说的致命错误了,那个不是程序的错误,而是A*搜索算法本身的弱点。这个KNUTH在他关于A*搜索算法的文章中提到过,就是当我制造一个很明显的错误,使题目无解时,算法可能需要遍历整个搜索树,才能告诉我无解。
相关文章可以到BAIDU去搜索dancinglink找到。
18
lazycat007 1个月前 回复
0
0
@njuaplusplus: 之前是我没有完全理解算法,所以才会那么说。当我实现了杀手数独的解题后,才对这个算法有了新的认识。这个程序只是实现了对普通数独的解题,对于杀手数独,因为缺少限制条件,完全没有办法,只能遍历。
19
lazycat007 1个月前 回复
0
0
关于11楼的那几行,我看过文章后知道,应该是用left才对,要完全反序恢复嘛。
20
njuaplusplus 1个月前 回复
0
0
@半瓶墨水: 不好意思。。之前在赶作业。。所以没有回复。。这个回复好像没有提醒功能呢~
没有过分测试,只是发现,可能程序无限递归回溯然后再递归,因为你测试之后将删除的结点全部放回了。。。目前猜测可能是这边的问题。。
这样讲不清楚。。
举个例子吧:
你去掉那个scnt>2,就是说我这里有个数独其存在多个不同的解,我想将这些解全部求出来,那么可能会一直进入上述状态,只重复找到多个解中的同一个解。。
你可以用个空的数独,即81个"."来测个20个解,打印出来,发现都是一样的。。
21
njuaplusplus 1个月前 回复
0
0
@lazycat007: 请问你是怎么用这个来改杀手数独的呢~
22
lazycat007 1个月前 回复
0
0
@njuaplusplus: 你说的这个问题应该不存在,本身算法就是可回溯的,必须要在函数返回后,将删除的结点进行恢复。因为函数在挑选列时,每次只找一个CNT最少的列,然后对这一列进行循环,所以每次函数返回,就会选择该列的下一个结点,也即下一种可能,因此不会有重复。现在这段代码只能显示2个解,需要稍微修改下,才能显示更多的解。

关于杀手数独,需要重新定义行,将每个区块的所有可能列出,然后再进行全排列,每个排列作为一行,列的设置不变。每行是这个区块的一个固定解(位置固定,候选数固定),这样就可以解杀手数独了。
23
lazycat007 1个月前 回复
0
0
...o|rz...评论被删除...
24
lazycat007 1个月前 回复
0
0
其中需要注意的是,如果是9区块,可以按照普通数独的方法设置,将9个候选数都设置进去即可。并且在行头上记录该行是哪些位置上有哪些候选数。在解题时,每选择一行,就记录该行对应的位置和候选数。
因为代码太多,就不往上发了。
25
lazycat007 1个月前 回复
0
0
@njuaplusplus: 请仔细看105~120行的代码。并请仔细检查得出的结果。
26
lazycat007 1个月前 回复
0
0
其实是代码写得太烂,不好意思往上发。

发表评论

注册登录后再发表评论