这次我们来谈一谈为什么罗伯法只能用于奇数阶的幻方。
首先还是放代码
public static boolean generateMagicSquare(int n) throws IOException {
if(n<0||n%2==0){
System.out.println("输入数据不合法,必须是正奇数才可以。");
return false;
}
int magic[][] = new int[n][n]; //创建magic二维数组,用于储存magic矩阵
int row = 0, col = n / 2, i, j, square = n * n; //根据输入的n来设定常量
for (i = 1; i <= square; i++) { //逐次生成magic矩阵里的所有数值
magic[row][col] = i; //从第0行的中间位置开始生成,从1开始,后一个生成的值比前一个大1
if (i % n == 0) { //生成好相当于一行数目的值之后就在下一行开始赋值,以免重复赋值
row++;
}
else {
if (row == 0) row = n - 1; //如果row对应第一行,就跳转为最后一行
else row--; //而如果不是,就将row-1,上移一行
if (col == (n - 1)) //对于col同理,只不过增减方向相反
col = 0;
else col++;
} }
try(FileWriter filewriter = new FileWriter("src\\P1\\txt\\6.txt")){
for (i = 0; i < n; i++) {
for (j = 0; j < n; j++)
filewriter.write(magic[i][j] + "\t");
filewriter.write("\n");
}
}
return true;
}
在上次的论述中,我们知道了幻方构建的大致流程。在这个程序里就是不停地向右上方移动读/写头,然后每到n的倍数时,切换一下写的行。想起了这些,就可以很轻松地意识到,当i为n的倍数,并且row值为n-1时,经过行数+1的代码段,row的值会直接变成n,出现数组越界,从而导致程序异常退出。
如果还不明白的话,就可以看看我下面的推演:以n=4的情况为例
首先,在n=4的情况中,row值为0,col值为n/2=4/2=2.因此写1的位置是row[0][2],和n=5的情况一致。
随后,我们按照之前的规律,从1一直写到8,就会发现当写完8之后,i值为4的倍数,并且row值为n-1,经过程序row值会变为n,直接越界。因此程序会异常退出。
那么很显然,将这条语句改为row=(row++)%n即可让偶数情况下不报错。
那么,是不是这样就可以让这个程序生成偶数阶的MagicSquare了呢?大家可以自己跑程序试一试,我这里就直接给出结果:不能
这是按照之前代码规律,生成的4阶幻方。第一行之和= 9 + 15 + 1 + 7 = 32,而第二行之和 = 14 + 4 + 6 + 12 = 36,显然不相等。由此我们能非形式化得出,这个程序只能适用奇数阶的幻方。
那有没有能够更加形式化的证明方法呢?
我们从每次i % n == 0 的情况入手,每次i % n == 0后,row的相对位置相比之前都会下降2位,设下降了j次,其中j为自然数。
我们需要对方程2*j =k*n进行求解,看是否有小于等于n的自然数解。
其中k是任意正整数。
在n为奇数的情况下,j*2仅在j=n的情况下整除n(n/2不是整数,取不到),只有当i==n时,row才会等于n;而在n为偶数情况下,j能取到n/2,使得程序中出现i<n但row=n的情况。
掌握了这点,这个程序就可以控制输入n的奇偶性:如果是奇数,就正常运行程序,如果是偶数的话及就会数组下标越界,直接报错。
那么,回到刚才的话题,为什么在偶数情况下生成的不是magic square呢?
这是因为,在这种数字循环出现重复的情况下,就会出现余数小的和小的加在一起,余数大的和大的加在一起的情况,结果就不是magic square了。
还是以这个图为例,我们已知第一横行的四个数之和为32,第二个横行的四个数之和为36,二者不等。 第一横行的四个数q值(不知道q值是什么的可以见详解1)分别是1,3,1,3,第二横行的四个q值分别是2,4,2,4,因此两个横行之和并不相等。