zoukankan      html  css  js  c++  java
  • 糟蹋好题——魔方阵问题

    输出"魔方阵"。所谓魔方阵是指这样的方阵,它的每一行、每一列和对角线之和均相等。例如,三阶魔方阵为
          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;   
    }
    

        

  • 相关阅读:
    单线程写的下载器
    java反射详解
    查找替换文本文件内容
    设计模式工厂模式
    java写的多项式乘法
    java中类的生命周期
    java写的回溯法求迷宫问题
    MVC:如何使用站点地图
    WPF: RoutedEvent
    Silverlight 如何:指定和检索自定义初始化参数
  • 原文地址:https://www.cnblogs.com/pmer/p/2429870.html
Copyright © 2011-2022 走看看