先来说说数独,数独这个经典的益智游戏,我以前也玩过,我觉得用数独作为个人项目的题目还挺有意思的。
数独的棋盘是一个9×9的格图,每3×3又是一个9宫格。
数独的要求是每行、每列、每个9宫格中,1~9这9个数字必须出现且仅出现一次。
了解了数独的规则之后,再来看看项目需求,第一个需求是要生成1e6种不同的数独终局。刚开始看到这个要求的时候是懵逼的,但是仔细想了一节课之后,发现了数独终局的算是一些性质吧。
举个例子,先来看一个合法的数独终局
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
7 | 8 | 9 | 1 | 2 | 3 | 4 | 5 | 6 |
4 | 5 | 6 | 7 | 8 | 9 | 1 | 2 | 3 |
9 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
6 | 7 | 8 | 9 | 1 | 2 | 3 | 4 | 5 |
3 | 4 | 5 | 6 | 7 | 8 | 9 | 1 | 2 |
8 | 9 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
5 | 6 | 7 | 8 | 9 | 1 | 2 | 3 | 4 |
2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 1 |
我们发现,对于这个数独终局,从第二行开始,每行分别是第一行右移3、6、1、4、7、2、5、8列的结果。
显然,对于任何一个1~9的全排列,都可以通过这种方式来得到一个终局。
这样就可以获得9!=362880种终局。
但是还要求左上角的格子必须是学号后两位之和mod 9 + 1,这样就只有8!=40320种终局。
我们还可以发现,对于任何一个数独终局的1~3行,我们任意交换这三行的顺序,得到的仍然是一个合法的终局,4~6行和7~9行同理,列也同理。
这样我们又可以在刚刚的基础上,按照这种方法扩展出很多种终局。
由于左上角不能动,我们只交换4~6行中的任意两行或者7~9行中的任意两行,这样在刚刚每种终局就可以变成3!×3!=36种终局,一共1451520种终局,已经超过了1e6,可以满足要求了。
编写代码的时候注意一下别算重了就行。
下面给出我的部分实现:
int shift[9] = { 0, 3, 6, 1, 4, 7, 2, 5, 8 }; for (int i = 0; i < 6 && n; i++) { if (i) { next_permutation(shift + 3, shift + 6); shift[6] = 2, shift[7] = 5, shift[8] = 8; } for (int j = 0; j < 6 && n; j++) { if (j) next_permutation(shift + 6, shift + 9); char row[10] = "123456789"; for (int k = 0; k < 40320 && n; k++) { if (k) next_permutation(row + 1, row + 9); for (int r = 0; r < 9; r++) { for (int c = 0; c < 9; c++) cout << row[(c + shift[r]) % 9] << ' '; cout << endl; } cout << endl; } } }
优化之后在我本地生成1e6组数独终局只要10s左右,效率还是很不错的。
欢迎各位dalao指点。