C ++
我将在此处介绍的是一种算法,并以3x3的情况为例进行说明。从理论上讲,它可以扩展到NxN情况,但是这将需要功能更强大的计算机和/或一些巧妙的调整。我会在介绍过程中提到一些改进。
在继续之前,让我们注意Sudoku网格的对称性,即以平凡的方式导致另一个网格的转换。对于块大小3,对称性如下:
水平对称
**The N=3 sudoku is said to consist of 3 "bands" of 3 "rows" each**
permute the three bands: 3! permutations = 6
permute the rows in each band: 3 bands, 3! permutations each =(3!)^3=216
垂直对称
**The N=3 sudoku is said to consist of 3 "stacks" of 3 "columns" each.**
the count is the same as for horizontal.
注意,网格的水平和垂直反射可以通过结合使用这些方法来实现,因此不需要计算它们。还有一个空间对称要考虑,它是转置的,这是的因子2
。这给出了总的空间对称性
2*(N!*(N!)^N)^2 = 2*(6*216)^2=3359232 spatial symmetries for the case N=3.
然后是另一个非常重要的对称性,称为重新标记。
Relabelling gives a further (N^2)!=9!=362880 symmetries for the case N=3. So the total
number of symmetries is 362880*3359232=1218998108160.
解决方案的总数不能简单地通过将对称唯一解的数量乘以该数字来找到,因为存在许多(小于1%)自同构解。这意味着对于这些特殊解决方案,有一个将它们映射到自身的对称操作,或者有多个将它们映射到同一其他解决方案的对称操作。
为了估算解决方案的数量,我分四个步骤解决了这个问题:
1. r[362880][12]
用数字0到8的所有可能排列填充数组(这是编程的,它在C中,因此我们不打算使用1到9。)如果您比较聪明,则会注意到第二个下标是12而不是9。这是因为在执行此操作时,请记住我们将其视为“行”,因此我们还要计算另外三个整数r[9,10,11] == 1<<a | 1<<b | 1<<c
,其中9,10,11分别指向第一,第二和第三堆栈a,b,c是该行的每个堆栈中存在的三个数字。
2 b
用3行带的所有可能解填充数组。为了使此值保持在合理范围内,请仅包括顶行为012,345,678的解决方案。我通过生成所有可能的中间行并r[0][10,11,12]
与进行与运算,以蛮力实现r[i][10,11,12]
。任何正值表示在同一平方中有两个相同的数字,并且该范围无效。当前两行有有效的组合时,我使用相同的技术搜索第三行(底部)。
我将数组的尺寸标注为b [2000000] [9],但该程序仅找到1306368解决方案。我不知道有多少个,所以我就这样离开了数组。实际上,这只是单个频段(在Wikipedia上验证)的可能解决方案的一半,因为我仅从当前值i
向上扫描第三行。通过交换第二行和第三行,可以轻松找到其余一半的解决方案。
首先,将信息存储在数组b
中的方式有些混乱。而不是使用每个整数存储0..8
在给定位置找到的数字,这里每个整数都考虑一个数字,0..8
并指出可以在哪些列中找到它。因此,b[x][7]==100100001
这表示对于解决方案x,在第0,5和8列(从右到左)中找到了数字7。这种表示的原因是,我们需要通过重新标记来生成频段的其余可能性,并且表示法使执行此操作变得很方便。
上面的两个步骤包括设置过程,大约需要一分钟(如果我删除了不必要的数据输出,则可能会少一些。下面的两个步骤是实际的搜索。)
3随机搜索前两个不冲突的条带的解(即在给定列中没有两次相同的数字。我们选择条带1的一个随机解,假设总置换为0,而条带2的一个随机解为随机排列,通常少于9999次尝试(第一阶段的命中率在数千范围内),并需要一秒钟的时间。通过排列,我的意思是,对于第二个波段,我们从b []中得到一个解。 [],其中第一行始终为012,345,678,并重新标记它,以便第一行上的任何可能的数字序列都是可能的。
4在步骤3中找到一个命中点后,为不与其他两个片段冲突的第三个片段寻找解决方案。我们不想只尝试一次,否则将浪费步骤3的处理时间。另一方面,我们不想为此付出过多的努力。
只是为了好玩,昨晚我以最愚蠢的方式做到了,但它仍然很有趣(因为很久以来它一无所获,所以突然发现了大量解决方案。)即使花费了一点点黑客,整夜都花了一个数据点一旦知道这不是一个有效的解决方案,(!z)
我就终止了最后一个k
循环(这使其运行速度快了将近9倍。)在搜索了所有1306368标准解决方案的所有362880重新标记后,它为整个网格找到了1186585个解决方案区块,总共有474054819840个可能性。第二阶段的命中率为4百万分之一。我将很快尝试使用随机搜索而不是扫描。只需几百万次尝试,它就应该给出一个合理的答案,而这只需几秒钟。
总体答案应为(362880 *(1306368 * 2))^ 3 *命中率= 8.5E35 *命中率。通过从问题中的数字进行回算,我预计命中率为1 / 1.2E14。到目前为止,我对单个数据点的了解是1 /(400000 * 1000),相差约一百万。这可能是偶然的异常,程序错误或数学错误。在运行更多测试之前,我不知道它是什么。
我今晚将其留在这里。文本有点草率,我会尽快整理一下,希望能再增加一些结果,也许还会谈几句话,说明如何使其更快以及如何将概念扩展到N = 4。我不认为我会对程序进行更多更改,但:-)
啊..程序:
#include "stdafx.h"
#define _CRT_RAND_S
#include <algorithm>
#include <time.h>
unsigned int n[] = { 0,1,2,3,4,5,6,7,8 }, r[362880][12], b[2000000][9],i,j,k,l,u,v,w,x,y,z;
int main () {
//Run through all possible permutations of n[] and load them into r[][]
i=0;
do {
r[i][9] = r[i][10] = r[i][11]=0;
for (l = 0; l < 9; l++){
r[i][l] = n[l];
r[i][9 + l / 3] |= 1 << n[l];
}
if((i+1)%5040==0) printf("%d%d%d %d%d%d %d%d%d %o %o %o %o \n"
,r[i][0],r[i][1],r[i][2],r[i][3],r[i][4],r[i][5],r[i][6],r[i][7],r[i][8],r[i][9],r[i][10],r[i][11],r[i][9]+r[i][10]+r[i][11]);
i++;
} while ( std::next_permutation(n,n+9) );
//Initialise b[][]
for (l = 0; l<2000000; l++) for (k = 0; k<9; k++) b[l][k]=0;
//fill b[][] with all solutions of the first band, where row0 ={0,1,2,3,4,5,6,7,8} and row1<row2
l=0;
for (i = 0; i<362880; i++)
if (!(r[0][9] & r[i][9] | r[0][10] & r[i][10] | r[0][11] & r[i][11])){printf("%d %d \n",i,l);
for (j=i; j<362880;j++)
if(!(r[0][9]&r[j][9] | r[0][10]&r[j][10] | r[0][11]&r[j][11] | r[j][9]&r[i][9] | r[j][10]&r[i][10] | r[j][11]&r[i][11] )){
for (k = 0; k < 9; k++){
b[l][r[0][k]]|=1<<k;
b[l][r[i][k]]|=1<<k;
b[l][r[j][k]]|=1<<k;
}
l++;
}
// printf("%d%d%d %d%d%d %d%d%d %o %o %o %o \n"
// ,r[i][0],r[i][1],r[i][2],r[i][3],r[i][4],r[i][5],r[i][6],r[i][7],r[i][8],r[i][9],r[i][10],r[i][11],r[i][9]+r[i][10]+r[i][11]);
// printf("%d%d%d %d%d%d %d%d%d %o %o %o %o \n"
// ,r[j][0],r[j][1],r[j][2],r[j][3],r[j][4],r[j][5],r[j][6],r[j][7],r[j][8],r[j][9],r[j][10],r[j][11],r[j][9]+r[j][10]+r[j][11]);
// printf("%d %d %o %o %o %o %o %o %o %o %o \n",i,l,b[l][0],b[l][1],b[l][2],b[l][3],b[l][4],b[l][5],b[l][6],b[l][7],b[l][8]);
}
// find a random solution for the first 2 bands
l=0;
do{
rand_s(&u); u /= INT_MIN / -653184; //1st band selection
rand_s(&v); v /= INT_MIN / -181440; //2nd band permutation
rand_s(&w); w /= INT_MIN / -653184; //2nd band selection
z = 0;
for (k = 0; k < 9; k++) z |= b[u][k] & b[w][r[v][k]];
l++;
} while (z);
printf("finished random after %d tries \n",l);
printf("found solution with top band %d permutation 0, and middle band %d permutation %d \n",u,w,v);
getchar();
// scan all possibilities for the last band
l=0;
for (i = 0; i < 362880; i++) for (j = 0; j < 1306368; j++){
z=0;
for(k=0;(k<9)&&(!z);k++) z|= b[u][k] & b[j][r[i][k]] | b[j][r[i][k]] & b[w][r[v][k]];
if (!z){ l++; printf("solution %d : i= %d j=%d",l,i,j); }
}
printf("finished bottom band scan at %d millisec \n", clock()); getchar();
}