N*N数阵就是N*N魔方,要求每行每列及对角线之和都相等。下面是大学时用TC写的一个算法,效率很高,找到所有4阶需要16秒,刚才增加三行代码消除对称的魔方,只需2秒即可找到所有的832种可能(不输出结果,只计数)。暂时还不知道标准答案,可能有遗漏或重复。
------------------------------------------------------------------------------------------------------------------------------------------------
求解N阶魔方,即将1、2、3…N*N放入N*N的矩阵中,使得每行每列及两条对角线上的N个数的和都相等(等于SUM=(N*N)*(1+N*N)/(2*N))。
主要算法及思想:
首先得到所有的N个数的和为SUM的组合情况,放在数组All[M][N]中,其中每一组中的N个数都是从小到大排列,M为所有的组的个数。Num[N*N]中存放了包含数字1、2…N*N的组的个数。Index[M*N]中存放了包含1、2…N*N的组的序号(以此序号可在All中找到对应的N个数),其中单元1-Num[1]中存放的是包含数字1的所有组的序号,单元Num[1]+1-Num[2]中存放了包含数字2的所有组的序号……。为方便得到包含任一个数字n的所有的组的序号,构造一存放查找偏移量的数组Offset[N*N],其中存放的是在Index数组中查找包含任一数字的组的序号时的起始偏移量,如:要找包含数字n的所有组,可以从Index数组中Offset[n]-Offset[n]+Num[n]单元得到组的序号,以此序号再到数组All中得到组中的N个数字。
由于每一个组中的数字都是从小到大排列的,要得到同样N个数字的其他排列方式,可以通过函数Rotat得到(N个数共有N!种排列方式)。
Matrix[N][N]中存放的是N*N阶矩阵的每一个元素。得到魔方的步骤如下:
1。取一组的N个数字,按一种排列方式,放入矩阵的第一行。
2。取一个包含第一行第一列中数字的组,按一种排列方式,放入矩阵的第一列。
3。取一个包含第一行第二列中数字的组,按一种排列方式,放入矩阵的第二列:
每一列列中的每一个数字(除第一行的),都要与同一行的已放置的数字可以在同一个组中,如果是两条对角线上的数字,还要考虑与已放好的对角线上的数字能存在于同一个组中。如果不能满足这个条件,则更换这一列的N的数字的排列方式,如果所有的排列方式都不行,则另选一组放入此列中,若所有的组的任一种排列方式都不能放入此列,则将前一列的N个数字换一个排列方式,以此类推。
通过函数CheckG(int group,int col)判断第group组能否放入第col列中;通过函数CheckR(int col)判断将某一组按某一方式放入第col列后,已有的所有元素能否同时存在。
从以上的分析知,必须记录第一行和每一列中放置的组的序号和排列方式,以便更改。这些信息存放在数组States[N+1][3]中,其中第一维表示第一行和N列,第二维的3个单元分别存放:组的序号,排列方式,在包含某一数字的所有组中的位置。
以下为N=3,M=8(SUM=15)时的一个实例分析:
All[8][3]:
0:1 5 9(该组的序号为0,用0表示该组)
1:1 6 8(该组的序号为1,用1表示该组)
2:2 4 9
3:2 5 8
4:2 6 7
5:3 4 8
6:3 5 7
7:4 5 6
三个数字的和为15的组合只有以上8中。
所有组中包含数字1的组有(用组的序号表示):0 1;包含数字2的组有:2 3 4;包含数字3的组有:5 6;……
Num[9]: 2 3 2 3 4 3 2 3 2
Index[24]:
(0 1)(2 3 4)(5 6)(2 5 7)(0 3 6 7)(1 4 7)(4 6)(1 3 5)(0 2)
(0 1)为包含数字1的组;(2 3 4)为包含数字2的组;(5 6)为包含数字3的组;……
Offset[9]:0 2 5 7 10 14 17 19 22
如要找包含数字5的组,可在Index中的10-10+4单元中找到组的序号0、3、6、7
(10=Offset[5-1];4=Num[5-1])
寻找魔方的步骤:
1。将第一组按第一种排列方式放入第一行,States[0][0]=States[0][1]=0;
1 5 9
0 0 0
0 0 0 (0表示还未放入数字)
2。考虑放入第一列:
找到包含数字1的所有组中的第一个组(0:1 5 9),即States[1][2]=0;States[1][0]=0;
由于此组已经用于放在第一行中,不符合条件。
找到包含数字1的所有组中的第二个组(1:1 6 8),即States[1][2]++;States[1][0]=1;
此组符合条件,按第一种排列方式将6 8放入第一列States[1][1]=0;
1 5 9
6 0 0
8 0 0
由于副对角线上的8和9不能同时出现在一个组中,不符合条件
按第二种排列方式将6 8放入第一列States[1][1]++;
1 5 9
8 0 0
6 0 0
副对角线上的6 9也不能同时出现在一组,不符合条件
由于没有其他的排列方式,只能另选一组
包含数字1的组都不能满足条件,故考虑更换第一行
3。将第一行中的N个数换一种排列方式:States[0][1]++;
1 9 5
0 0 0
0 0 0
4。考虑放入第一列:
States[1][2]=0;States[1][0]=0;此组已放入第一行
States[1][2]++;States[1][0]=1;States[1][1]=0;
1 9 5
6 0 0
8 0 0
5。考虑放入第二列:
找到包含数字9的所有组中的第一个组(0:1 5 9),即States[2][2]=0;States[2][0]=0;
由于此组已用于第一行,不符合条件。
找到包含数字9的所有组中的第二个组(2:2 4 9),即States[2][2]++;States[2][0]=2;
按第一种排列方式States[2][1]=0;
1 9 5
6 2 0
8 4 0
由于主对角线上的1 2不能同时存在于一组中,不符合条件
States[2][1]++;
1 9 5
6 4 0
8 2 0
两条对角线均不符合条件
包含数字9的组均不符合条件,考虑更换第一列
……
最终找到一个魔方:
6 1 8
7 5 3
2 9 4