魔方陣的構造


       由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;

}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM