由n*n個數字所組成的n階方陣,若具有各對角線、各橫列與縱行的數字和都相等的性質,則稱為魔方陣。這個相等的和稱為魔術數字。若填入的數字是從1到n*n,稱此種魔方陣為n階正規魔方陣。
如下所示為一個3階魔方陣和一個四階魔方陣。

魔方陣的構建方法很多,一般將n分為三類,這三類n構成的魔方陣的算法各不相同。
(1)當n為奇數,即n=2*k+1時,常采用簡捷連續填數法。
(2)當n為單偶數(n是偶數,但又不能被4整除),即n=4*k+2時,常采用井字調整法。
(3)當n為雙偶數(n能被4整除),即n=4*k時,常采用雙向翻轉法。
- 構造奇數階魔方陣
奇數階魔方陣的構造方法為:
首先把1放到頂行的正中間,然后把后繼數按順序放置在右上斜的對角線上,並作如下修改:
(1)當到達頂行時,下一個數放到底行,好像它在頂行的上面;
(2)當到達最右端列時,下一個數放在最左端列,好像它緊靠在右端列的右方;
(3)當到達的位置已經填好數時,或到達右上角的位置時,下一個數就放在剛填寫數的位置的正下方。
下面以構造一個3階魔方陣為例,說明這種方法的構造過程,具體如圖1所示。
圖1 簡捷連續填數法構造3階魔方陣
- 編程思路
程序中定義一個二維數組a[N][N]來保存方陣,初始時,數組中所有元素均置0。
用變量row和col來存儲待填數字num在方陣中的位置,由於第1個數字放在頂行的正中間,因此初始時,行row=0,列col=n/2,待填寫數字num=1。
采用簡捷連續填數法構造方陣的過程是一個循環程序,描述為:
While (待填寫數字num<=n*n)
{
確定待填寫數字num應該填寫的位置row和col;
填寫num,即a[row][col]=num;
Num++; // 下一個待填寫的數字
}
程序中,確定待填寫位置的方法是:
(1)后繼數按順序放置在右上斜的對角線上,即row--; col++;
(2)有三種情形需要調整。
當到達頂行時(即row<0), row=n-1;
當到達最右端列時(即col==n), col=0;
當到達的位置已經填好數時(即(a[row][col]!=0), row+=2; col--;
(3)有一種情況,當到達右上角的位置時(row==0 && col==n-1),直接進行特殊處理,row++ 。
- 源程序及運行結果
#include <iostream>
#include <iomanip>
using namespace std;
int main()
{
int a[9][9],row,col,num,n;
cin>>n;
for (row=0;row<n;row++) // 初始化,數組中所有元素均置0
for (col=0;col<n;col++)
a[row][col]=0;
row=0; col=n/2; num=1;
a[row][col]=num;
while (num<n*n)
{
num++;
if (row==0 && col==n-1) // 到達右上角的位置
row++;
else
{
row--; col++;
if (row<0) row=n-1;
if (col==n) col=0;
if (a[row][col]!=0)
{ row+=2; col--; }
}
a[row][col]=num;
}
for (row=0;row<n;row++)
{
for (col=0;col<n;col++)
cout<<setw(4)<<a[row][col]<<" ";
cout<<endl;
}
return 0;
}
- 構造雙偶數階魔方陣
當n為雙偶數,即n=4*k時,采用雙向翻轉法。雙向翻轉法構造魔方陣的步驟如下:
(1)將數字1到n*n按由左至右、由上到下的順序填入方陣中。
(2)將方陣中央部分半數的行中的所有數字左右翻轉。
(3)將方陣中央部分半數的列中的所有數字上下翻轉。
由於在構造的過程中需要進行兩次翻轉,因此稱為雙向翻轉法。下面以構造一個4階魔方陣為例,說明這種方法的構造過程,具體如圖2所示。
圖2 雙向翻轉法構造4階魔方陣
- 編程思路
程序中定義一個二維數組a[N][N]來保存方陣,構造時,依次進行三個二重循環。
(1)將數字1到n*n按由左至右、由上到下的順序填入方陣中
num=1;
for (row=0; row<n; row++)
for (col=0; col<n; col++)
a[row][col] = num++;
(2)將方陣中央部分半數的行中的所有數字左右翻轉
對於一個n=4*k階的雙偶數方陣,若按行分成四組的話,每組行號的范圍為0~k-1、k~2k-1、2k~3k-1、3k~4k-1,中間有2k行,中間行的行號從k~3k-1,由於k=n/4,所以中間行的行號從n/4~n*3/4-1。
對於每一行,將其中的所有數字左右翻轉,實際上就是將一個一維數組逆序排列。
因此,第2步的操作可以寫成如下的循環:
for (row=n/4; row<=n*3/4-1; row++)
for (col=0; col<n/2; col++)
{
temp = a[row][col];
a[row][col] = a[row][n-1-col];
a[row][n-1-col] = temp;
}
(3)將方陣中央部分半數的列中的所有數字上下翻轉。
第3步的操作類同於第2步的操作,只是將行列的關系顛倒了,可以寫成如下的循環:
for (col=n/4; col<=n*3/4-1; col++)
for (row=0; row<n/2; row++)
{
temp = a[row][col];
a[row][col] = a[n-1-row][col];
a[n-1-row][col] = temp;
}
- 源程序及運行結果
#include <iostream>
#include <iomanip>
using namespace std;
#define SIZE 20
int prove(int a[][SIZE],int n);
int main()
{
int a[SIZE][SIZE],row,col,num,n,temp;
cin>>n;
num=1;
for (row=0; row<n; row++)
for (col=0; col<n; col++)
a[row][col] = num++;
for (row=n/4; row<=n*3/4-1; row++)
for (col=0; col<n/2; col++)
{
temp = a[row][col];
a[row][col] = a[row][n-1-col];
a[row][n-1-col] = temp;
}
for (col=n/4; col<=n*3/4-1; col++)
for (row=0; row<n/2; row++)
{
temp = a[row][col];
a[row][col] = a[n-1-row][col];
a[n-1-row][col] = temp;
}
for(row=0;row<n;row++)
{
for(col=0;col<n;col++)
cout<<setw(4)<<a[row][col]<<" ";
cout<<endl;
}
return 0;
}
- 構造單偶數階魔方陣
當n為單偶數,即n=4*k+2(6、10、14、18、22、26、30…)時,采用井字調整法。井字調整法構造魔方陣的步驟如下:
(1)將數字1到n*n按由左至右、由上到下的順序填入方陣中,然后在第k + 1、3 k + 2 行及列做井字標記。
(2)將井字兩邊長方形中的數字和其對稱位置的數字交換。注:坐標( x, y) 的對稱位置為 ( n + 1 – x,n + 1 - y )。
(3)將井字分隔線的兩橫行及第k + 2行兩側的數字左右對調,兩橫行中央的數字上下對調。左邊縱列的數字除交叉點外垂直翻轉。
(4)將井字分隔線的兩縱列中央的數字除第 2k+1行外左右對調,兩橫行左方的第一個數字上下對調,上橫行中央的數字水平翻轉。
在構造單偶數階魔方陣的過程中,為了便於識別,構造時在方陣中有井字形的縱橫線標記,因此稱為井字調整法。
圖3所示為一個6(4*1+2,k=1)階魔方陣的構造過程,具體步驟如下:
(1)先將數字1~36順序填入方陣,然后在第 2(1+1)、5(3*1+2) 列及第 2、5 行做上井字形標記,如圖3(1)所示。
(2)將井字兩邊長方形中的數字與其對稱位置的數字交換,如圖3(2)所示,圖中交換的數字用黑框標出。
(3)將第 2、3、5行兩側的數字左右對調,第 2、5行中央的數字上下對調,第 2列的數字除交叉點外進行垂直翻轉,如圖3(3)所示,圖中交換的數字依次用波浪線框、單線框和黑框標出。
(4)第 2、5列中央的數字(第3行中的數字除外)左右對調,第 2行中央的數字水平翻轉,第 2、5行左邊的第一個數字上下對調,如圖3(4)所示,圖中交換的數字依次用波浪線框、單線框和黑框標出。
圖3 井字調整法構造6階魔方陣
- 編程思路
為構造單偶數階魔方陣,在將數字1到n*n按由左至右、由上到下的順序填入方陣后,需要進行三大步的調整。
(1)井字兩邊長方形中的數字與其對稱位置的數字交換
井字兩邊的長方形共四塊,其中上下兩塊互為對稱,需要進行互換;左右兩塊互為對稱,也需要進行互換。因此,只需考慮上面和左邊的兩塊的操作方法即可。
上面一塊長方形的行號范圍為0~k-1(注意:程序中數組下標從0開始,而前面算法描述中,井字標記的行號從1開始),列號范圍為k+1~3k,對於這塊長方形區域中的任一格子(row,col),其對稱位置為(n-1-row,n-1-col),因此,上下數字互換可以寫成一個循環。
k=(n-2)/4;
for (row=0; row<=k-1;row++)
for (col=k+1; col<=3*k; col++)
a[row][col] 和a[n-1-row][n-1-col]交換;
同理,左邊一塊長方形的行號范圍為k+1~3k,列號范圍為0~k-1,對於這塊長方形區域中的任一格子(row,col),其對稱位置也為(n-1-row,n-1-col),因此,左右數字互換可以寫成一個循環。
k=(n-2)/4;
for (row= k+1; row<=3*k;row++)
for (col=0; col<= k-1; col++)
a[row][col] 和 a[n-1-row][n-1-col];
實際上,由於上面一塊長方形和左邊一塊長方形關於對角線對稱,即上面一塊長方形中格子的坐標(row,col)變換為(col,row)即為左邊長方形中相應格子的坐標,因此,上面的兩個循環可以合並為一個循環。
k=(n-2)/4;
for (row=0; row<=k-1;row++)
for (col=k+1; col<=3*k; col++)
{
a[row][col] 和a[n-1-row][n-1-col] 交換;
a[col][row] 和 a[n-1-col][n-1-row] 交換;
}
(2)第3步之井字分隔線的兩橫行及第k + 2行兩側的數字左右對調
由於是左右對調,因此考慮左邊的情況,列號范圍為0~k-1。操作可以寫成如下循環:
for (col=0; col<=k-1;col++)
{
a [k][col] 和 a [k][ n-1-col]交換; // 井字分隔線第k + 1
a [3*k+1][col] 和 a [3*k+1][ n-1-col]交換; // 井字分隔線第3k + 2
a [k+1][col] 和 a [k+1][ n-1-col]交換; // 第k + 2行
}
(3)第3步之井字分隔線的兩橫行中央的數字上下對調
井字分隔線的兩橫行中央區域的列號范圍為k+1~3k,數字上下互換可寫成一個循環。
for (col=k+1; col<=3*k ;col++)
a [k][col] 和 a [3*k+1][ col]交換;
(4)第3步之井字分隔線的左邊列除交叉點外的數字垂直翻轉
井字分隔線的左邊列的列號范圍為k,數字垂直翻轉就是逆序,但交叉點(行號為k)除外,因此可寫成一個循環。
for (row=0; row<n/2 ; row++)
if(row!=k) a [row][k] 和 a [n-1-row][k]交換;
(5)將井字分隔線的兩縱列中央的數字除第 2k+1行外左右對調,兩橫行左方的第一個數字上下對調,上橫行中央的數字水平翻轉。
井字分割線兩橫行左方的第一個數字上下對調可寫為:
a [k][0] 和 a [3*k+1][0]交換;
上橫行中央的數字水平翻轉可寫為:
for (col=k+1; col<n/2 ;col++)
a [k][col] 和 a [k][n-1- col]交換;
井字分隔線的兩縱列中央區域的行號范圍為k+1~3k,因此,井字分隔線的兩縱列中央的數字除第 2k+1行外左右對調可寫為:
for (row=k+1; row<=3*k ; row++)
if(row!=2*k) a [row][k] 和 a [row][3*k+1]交換;
(6)兩個數字交換寫成一個函數
由於在調整時,涉及到較多的數字交換,因此將其寫成一個函數,實現如下:
void swap(int *x,int *y)
{
int t;
t=*x; *x=*y; *y=t;
}
- 源程序及運行結果
#include <iostream>
#include <iomanip>
using namespace std;
#define SIZE 20
int prove(int a[][SIZE],int n);
void swap(int *x,int *y)
{
int t;
t=*x; *x=*y; *y=t;
}
int main()
{
int a[SIZE][SIZE],row,col,num,n,k;
cin>>n;
num=1;
for (row=0; row<n; row++)
for (col=0; col<n; col++)
a[row][col] = num++;
k=(n-2)/4;
for (row=0; row<=k-1;row++)
for (col=k+1; col<=3*k; col++)
{
swap(a[row][col],a[n-1-row][n-1-col]);
swap(a[col][row],a[n-1-col][n-1-row]);
}
for (col=0; col<=k-1;col++)
{
swap(a[k][col],a[k][n-1-col]); // 井字分隔線第k + 1
swap(a[3*k+1][col], a[3*k+1][n-1-col]); // 井字分隔線第3k + 2
swap(a[k+1][col], a[k+1][n-1-col]); // 第k + 2行
}
for (col=k+1; col<=3*k ;col++)
swap(a[k][col], a[3*k+1][col]);
for (row=0; row<n/2 ; row++)
if(row!=k) swap(a[row][k], a[n-1-row][k]);
swap(a[k][0], a[3*k+1][0]);
for (col=k+1; col<n/2 ;col++)
swap(a[k][col] , a[k][n-1-col]);
for (row=k+1; row<=3*k ; row++)
if(row!=2*k) swap(a[row][k] , a[row][3*k+1]);
for(row=0;row<n;row++)
{
for(col=0;col<n;col++)
cout<<setw(4)<<a[row][col]<<" ";
cout<<endl;
}
return 0;
}