糟蹋好題——魔方陣問題


輸出"魔方陣"。所謂魔方陣是指這樣的方陣,它的每一行、每一列和對角線之和均相等。例如,三階魔方陣為
      8 1 6
      3 5 7
      4 9 2
要求輸出1~n*n的自然數構成的魔方陣。
解:魔方陣中各數的排列規律如下:
(1)將1放在第1行的中間一列。
(2)從2開始直到n×n止各數依次按下列規則存放:每一個數存放的行比前一個數的行數減1,
列數加1(例如上面的三階魔方陣,5在4的上一行后一列)。
(3)如果上一數的行數為1,則下一個數的行數為n(指最下一行)。例如,1在第一行,則2應放在最下一行,
列數同樣加1.
(4)當上一個數的列數為n時,下一個數的列數應為1,行數減1.例如,2在第3行最后一列,
則3應放在第2行第1列。
(5)如果按上面規則確定的位置上已有數,或上一個數是第一行第n列時,則把下一個數放在
上一個數的下面。例如,按上面的規定,4應該放在第1行第2列,但該位置已經被1占據,
所以4就放在3的下面。由於6是第1行第3列(即最后一列),故7放在6下面。
按此方法可以得到任何階的魔方陣。

    ——譚浩強 ,《C程序設計(第四版)學習輔導》,清華大學出版社,2010年7月,p61

  這個題目可以使用二維數組作為基本的數據結構,題目解法主要是向二維數組中填寫數據。作為數組的練習題,本應該是一個很好的題目。然而很可惜,這個好題目卻被糟蹋了。
  首先,題目本身有錯誤:

要求輸出1~n*n的自然數構成的魔方陣

  這本身就是一個錯誤的要求,因為有些偶數階(例如2階)的魔方陣根本不存在,而且從后面的代碼來看,題目根本沒有輸出偶數階魔方陣的意圖。
  求解這樣的問題有害無益而且貽害無窮,因為這使得程序員喪失了必要的職業嚴謹,對錯誤的需求變得麻木不仁。
  軟件開發過程中最嚴重的錯誤,從來不是代碼中的錯誤,也不是設計的錯誤,最要命的是需求本身是錯的。據統計,由需求錯誤而導致的軟件錯誤占錯誤總數的一半以上。
  要求模糊不清是這個題目的另一個錯誤。問題並沒有明確n的范圍,而對於不同范圍的n數據結構和算法完全不同。要求模糊帶來的另一個嚴重問題是根本無法測試。
  所以問題的正確提法應該是:
  輸入一個1~15之間的奇數n,輸出1~n*n的自然數所構成的魔方陣。
  再來看代碼和運行結果:

View Code
#include <stdio.h>
int main()
{ int a[15][15],i,j,k,p,n;
p=1;
while(p==1)
{printf("enter n(1--15):"); //要求階數為1~15之間的奇數
scanf("%d",&n);
if((n!=0)&&(n<=15)&&(n%2!=0))
p=0;
}
//初始化
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
a[i][j]=0;+++
//建立魔方陣   
j=n/2+1;
a[1][j]=1;
for(k=2;k<=n*n;k++)
{i=i-1;
j=j+1;
if((i<1)&&(j>n))
{i=i+2;
j=j-1;
}
else
{if(i<1)i=n;
if(j>n)j=1;
}
if(a[i][j]==0)
a[i][j]=k;
else
{i=i+2;
j=j-1;
a[i][j]=k;
}
}
//輸出魔方陣
for(i=1;i<=n;i++)
{for(j=1;j<=n;j++)
printf("%5d",a[i][j]);
printf("\n");
}
return 0;
}

運行結果:
enter n(1--15):5
   17   24    1    8   15
   23    5    7   14   16
   4     6   13   20   22
   10   12   19   21    3
   11   18   25    2    9

    ——譚浩強 ,《C程序設計(第四版)學習輔導》,清華大學出版社,2010年7月,p61~62
  令人詫異的是,這段代碼由於存在錯誤根本無法通過編譯。
  更令人詫異的是書中居然給出了運行結果,這個運行結果究竟是如何得到的呢?不得而知。

  在代碼第15行存在一個語法錯誤

a[i][j]=0;+++

  這種低級幼稚的編譯錯誤只可能出現在初學者的代碼之中,任何熟練的C語言編程者的代碼,哪怕只要編譯過一次,這種錯誤都不可能存留其中。

  下面分析代碼中的其他問題:

int a[15][15],i,j,k,p,n;

  考慮到題目出現時的背景知識(只學過數組,尚未學習函數、指針)

int a[15][15],n;

  這種數據結構尚屬合理,但是15作為Magic Number有瑕疵。
  至於 “i,j,k,p,”,則屬於爛得不能再爛的名字,而且這幾個變量根本就毫無必要。

  p=1;
while(p==1)
{printf("enter n(1--15):"); //要求階數為1~15之間的奇數
scanf("%d",&n);
if((n!=0)&&(n<=15)&&(n%2!=0))
p=0;
}

  這段代碼的本意是使程序具有一定的健壯性:在輸入不滿足要求的情況下實現重新輸入。就其本意來說無可指責,但就其實現來說則是像假肢一樣笨拙無比,僵硬造作,大概那個p是prosthesis(假肢)的縮寫。因為這個p根本毫無必要。即使使用

while(1)
{
   if()
   {
     break ;
   }
}

這種結構也比原來的寫法強。
  必須要說的s是,這段代碼根本就是錯的。輸入-3這種錯誤數據時沒有起到任何容錯的作用。(n!=0)、(n%2!=0)這兩個條件是無意義的重復,n%2!=0時難道還用得着判斷n!=0嗎?根本沒必要,而且(n!=0)這個條件根本就是邏輯錯亂。

//初始化
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
a[i][j]=0;

  這段代碼非常愚蠢,因為其功能只要在定義數組時

int a[15][15]={0};

簡單地初始化就能實現。

//建立魔方陣   
j=n/2+1;
a[1][j]=1;

  這段代碼明顯是錯誤的,因為C語言的數組的下標從0開始,而且輸入為15時明顯會發生數組越界。此外這段代碼寫在循環內部為好。

  {i=i-1;
j=j+1;

  這段代碼位置不當。這個計算早了,過后可能還得重算,所以算了也是白算。  

if((i<1)&&(j>n))

實際上應該寫為

if((i==0)&&(j==n+1))

  把條件放寬不但表明缺乏代碼自信,而且也為BUG留了一個后門。

   if(a[i][j]==0)
a[i][j]=k;
else
{i=i+2;
j=j-1;
a[i][j]=k;
}

  這段代碼的可笑之處在於有if和else后面有兩個完全一樣的“a[i][j]=k; ”
  其實這段代碼完全可以簡潔地寫為:

   if(a[i][j]!=0)
   {
      i=i+2;
      j=j-1;
   }
   a[i][j]=k;

  另外這條if語句與前面一個if語句並列是一個邏輯錯誤。
  最后一段代碼錯誤太明顯了

      //輸出魔方陣
for(i=1;i<=n;i++)
{for(j=1;j<=n;j++)
printf("%5d",a[i][j]);
printf("\n");
}

  n的值為15時顯然存在數組越界的錯誤。
  一道很好的練習題,被譚先生的教科書給糟蹋成這個樣子,實在是令人無語。

  下面是這道題的兩種參考寫法:

代碼1:

/*
作者:hbmhalley 
引自:http://bbs.chinaunix.net/thread-3693406-2-1.html
*/
#include <stdio.h>

#define LEN 15

int main (void) {
        int i , n , x , y ;
        int a[LEN][LEN] = {} ;

        do
                printf ("enter n (1~%d): " , LEN) ;
        while (! (scanf ("%d" , &n) == 1
                                && n > 0 && n <= LEN && n%2 == 1)) ;

        x = 0 ; y = n/2 ;
        for (i = n*n ; i >= 1 ; --i) {
                a[x][y] = i ;
#define P(k) (((k) + n) % n)
                (x == 0 && y == n-1 || a[P(x-1)][P(y+1)] != 0)
                        ? (x = P(x+1))
                        : (x = P(x-1) , y = P(y+1)) ;
#undef P
        }

        for (x = n-1 ; x >= 0 ; --x , puts(""))
                for (y = n-1 ; y >= 0 ; --y)
                        printf ("%3d" , a[x][y]) ;
        puts("") ;

        return 0 ;
}

代碼2:

/*
輸入一個1~15之間的奇數n,輸出1~n*n的自然數所構成的魔方陣。
*/

#include <stdio.h>

#define MIN   1
#define MAX   15
#define EMPTY 0


int main( void )
{
  int magic_square[MAX][MAX] = { EMPTY } ;
  int n ;
  
  //輸入階數
  {
      printf("輸入階數(1~15之間的奇數):");
      while( scanf("%d",&n) == 0 
          || ( n <  MIN   ) 
          || ( n >  MAX   ) 
          || ( n % 2 == 0 )  )
      {
          while( getchar() != '\n')
              ;
          printf("輸入不正確,請重新輸入:");
      }
  }
  
  //填充
  {
      int num ;
      const int beg = 1 , end = n * n ; // int beg = 1 , end = n * n ;
      int row , col ;
      for( num = beg ; num <= end ; num ++) {
         if( num ==  beg ) {
            row = 0 ;
            col = n / 2 ;
         }
         else {
            if( row == 1 - 1 &&  col == n - 1 )//右上角 
               row ++ ;
            else {
               int row_n = ( row - 1 + n ) % n ;
               int col_n = ( col + 1 ) % n ;
               if( magic_square [row_n][col_n] != EMPTY )//下個位置已填 
                  row ++ ;
               else {
                  row = row_n ;
                  col = col_n ;
               }
            }
         }
         magic_square [row][col] = num ;
      }
  }
  
  //輸出
  {
      int row , col ;
      for( row = 0 ; row < n ; row ++ )
      {
         for( col = 0 ; col < n ; col ++ )
            printf("%5d" , magic_square[row][col]);
         putchar('\n');
      }
  }
  return 0;   
}

 

    


免責聲明!

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



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