幻方,中国古代称为河图、洛书,又叫纵横图,是一种传统游戏。旧时在官府、学堂多见;如今在中小学竞赛、大学考研、考博中也多有涉及。

幻方
1.考博原题
打印魔方阵,所谓魔方阵是这样的方阵,它的每一列,每一行和对角线之和均相等。
例如:三阶魔方阵为:

3阶魔方阵
写一程序能打印出由1到n2(n≤15)的自然数构成的魔方阵。
(中国矿业大学,2002年,C语言程序设计,20分)
2.幻方定义
幻方也称纵横图、魔方、魔阵,是横行、竖列、对角线上数的和都相等的数的方阵。n阶幻方是由前n2个自然数组成的一个n阶方阵,其各行、各列及两条对角线所含的n个数的和相等。
幻方按照阶数的性质可分为:
(1)奇数阶幻方:当n为奇数时,如:1,3,5,…;
(2)双偶阶幻方:当n为4的倍数时,如:4,8,12,…;
(3)单偶阶幻方:当n为偶数,且不是4的倍数时,如:6,10,14,…。
3.幻方算法
3.1奇数阶幻方(罗伯法)
假定阵列的行、列下标都从0开始,则魔方阵的生成方法为:
在第0行中间置1,对从2开始的其余n2-1个数依次按下列规则存放:
(1) 假定当前数的下标为(i,j),则下一个数的放置位置为当前位置的右上方,即下标为(i-1,j+1)的位置。
(2) 如果当前数在第0行,即i-1小于0,则将下一个数放在最后一行的下一列上,即下标为(n-1,j+1)的位置。
(3) 如果当前数在最后一列上,即j+1大于n-1,则将下一个数放在上一行的第一列上,即下标为(i-1,0)的位置。
(4) 如果当前数是n的倍数,则将下一个数直接放在当前位置的正下方,即下标为(i+1,j)的位置。
3.2双偶阶幻方(海尔法)
由定义知,n阶幻方中最大的数为n2,最小的数为1,其和为n2+1。
定义:在n阶幻方中,如果两个数的和等于n2+1,我们称它们为一对互补数。如在三阶幻方中,每一对和为10的数,是一对互补数 ;在四阶幻方中,每一对和为17的数,是一对互补数。
(1)先把数字按顺序填。然后,按4×4把它分割成4块。

海尔法第一步:顺序填数
(2)每个小方阵对角线上的数字,换成和它互补的数。

海乐法第二步:小方阵对角线换为互补数
3.3单偶阶幻方(斯特拉兹法)
单偶阶幻方的阶数n满足:n=4k+2 (k=1,2,3,…)。
以k=2,即10阶幻方为例介绍斯特拉兹法。
(1)把魔方阵分为A,B,C,D四个象限,每一个象限必定是奇数阶。用罗伯法,依次在A象限,D象限,B象限,C象限按奇数阶幻方的填法填数。

罗伯法填4个分块方阵
(2)在A象限的中间行、中间格开始,按自左向右的方向,标出k格。A象限的其它行则标出最左边的k格。将这些格,和C象限相对位置上的数互换位置。

选择A象限被替换区域

A象限与C象限对应位置元素互换
(3)在B象限所有行的中间格,自右向左,标出k-1格。(注:6阶幻方由于k-1=0,所以不用再作B、D象限的数据交换),将这些格,和D象限相对位置上的数互换位置。

选择B象限被替换区域

B象限与D象限对应位置元素互换
4.C程序源码
#include<stdio.h>
#include<stdlib.h>
//奇数阶(罗伯法)
int laobo(int n,int **arr,int num){
int i, j, k,N;
i = 0;
j = n / 2; //第1个元素位置
N=n*n-1;
for (k = num;k <=num+N;k++){//num代表第一个数(一般是以1开始)
arr[i][j] = k;
int x=(i - 1 + n) % n,y=(j + 1 + n) % n;
if (arr[x][y] == 0){//右上方可前进
i = x;
j = y;
}else{
i= (i + 1 + n) % n;
}
}
return 0;
}
//双偶阶(海尔法)
int Init(int n, int **arr){//初始化数组
int i, j, num;
num = 1;
for (i = 0;i < n;i++)
{
for (j = 0;j < n;j++)
{
arr[i][j] = num++;
}
}
return 0;
}
int haier(int n, int **arr){
int i, j, complement, deg;
complement = n*n + 1;//互补数:n*n+1
deg = n / 4; //分块:A,B,C,D
for (i = 0;i < deg;i++){//每一块
for (j = 0;j < deg;j++){//对角线取互补数
arr[i * 4 + 0][j * 4 + 0] = complement - arr[i * 4 + 0][j * 4 + 0];
arr[i * 4 + 0][j * 4 + 3] = complement - arr[i * 4 + 0][j * 4 + 3];
arr[i * 4 + 1][j * 4 + 1] = complement - arr[i * 4 + 1][j * 4 + 1];
arr[i * 4 + 1][j * 4 + 2] = complement - arr[i * 4 + 1][j * 4 + 2];
arr[i * 4 + 2][j * 4 + 1] = complement - arr[i * 4 + 2][j * 4 + 1];
arr[i * 4 + 2][j * 4 + 2] = complement - arr[i * 4 + 2][j * 4 + 2];
arr[i * 4 + 3][j * 4 + 0] = complement - arr[i * 4 + 3][j * 4 + 0];
arr[i * 4 + 3][j * 4 + 3] = complement - arr[i * 4 + 3][j * 4 + 3];
}
}
return 0;
}
////单偶阶(斯特拉兹法)
int late(int n, int **arr)
{
int deg=n/2;
int i,j,k,m,temp;
int **a;
a = (int **)malloc(sizeof(int*)*deg);
for (m = 0;m < deg;m++)
a[m] = (int *)malloc(sizeof(int)*deg);
for (i = 0;i < deg;i++)
for (j = 0;j < deg;j++)
a[i][j] = 0;
laobo(deg, a, 1);
for (i = 0;i < deg;i++)//A象限赋值
for (j = 0;j < deg;j++){
arr[i][j] = a[i][j];
a[i][j] = 0;
}
laobo(deg, a, 1+deg*deg);
for (i = 0;i < deg;i++)//D象限赋值
for (j = 0;j < deg;j++){
arr[i+deg][j+deg] = a[i][j];
a[i][j] = 0;
}
laobo(deg, a, 1 + 2*deg*deg);
for (i = 0;i < deg;i++)//B象限赋值
for (j = 0;j < deg;j++){
arr[i][j + deg] = a[i][j];
a[i][j] = 0;
}
laobo(deg, a, 1 + 3*deg*deg);
for (i = 0;i < deg;i++)//C象限赋值
for (j = 0;j < deg;j++){
arr[i + deg][j] = a[i][j];
a[i][j] = 0;
}
k = (n - 2) / 4;
for (i = 0;i < deg;i++){//AC象限前k个数的交换
for (j = 0;j < k;j++){
temp = arr[i][j];
arr[i][j] = arr[i + deg][j];
arr[i + deg][j] = temp;
}
}
//因为A象限中间行是从中间格开始换的,
//所以我们要将前面替换了的前k格给替换回来
for (j = 0;j < k;j++){
temp = arr[deg / 2][j];
arr[deg / 2][j] = arr[deg / 2 + deg][j];
arr[deg / 2 + deg][j] = temp;
}
//替换中间格开始的k个
for (j = deg/2;j <((deg/2) + k);j++){
temp = arr[deg / 2][j];
arr[deg / 2][j] = arr[deg / 2 + deg][j];
arr[deg / 2 + deg][j] = temp;
}
if (k != 0)
for (i = 0;i < deg;i++)
for (j = deg + deg / 2;j < ((deg + deg / 2) + k - 1);j++)
{
temp = arr[i][j];
arr[i][j] = arr[i + deg][j];
arr[i + deg][j] = temp;
}
return 0;
}
int main()
{
int **arr;//二级指针动态申请二维数组
int n,i, j;
printf("请输入幻方阶数(n):");
scanf("%d",&n);
arr = (int **)malloc(sizeof(int*)*n);//申请一个n*n的二维数组
for (i = 0;i < n;i++)
arr[i] = (int *)malloc(sizeof(int)*n);
for (i = 0;i < n;i++)
for (j = 0;j < n;j++)
arr[i][j] = 0;
if (n % 2 != 0)
laobo(n, arr,1);
else if (n % 4 == 0){
Init(n, arr);
haier(n, arr);
}else
late(n, arr);
for (i = 0;i < n;i++){
for (j = 0;j < n;j++)
printf("%4d", arr[i][j]);
printf("\n");
}
return 0;
}