zoukankan      html  css  js  c++  java
  • C语言初学者代码中的常见错误与瑕疵(5)

    问题:

    素数

    在世博园某信息通信馆中,游客可利用手机等终端参与互动小游戏,与虚拟人物Kr. Kong 进行猜数比赛。

    当屏幕出现一个整数X时,若你能比Kr. Kong更快的发出最接近它的素数答案,你将会获得一个意想不到的礼物。

     

    例如:当屏幕出现22时,你的回答应是23;当屏幕出现8时,你的回答应是7;

    若X本身是素数,则回答X;若最接近X的素数有两个时,则回答大于它的素数。

     

    输入:第一行:N 要竞猜的整数个数

    接下来有N行,每行有一个正整数X

    输出:输出有N行,每行是对应X的最接近它的素数

     

    样例:输入

    4

    22

    5

    18

    8

    输出

    23

    5

    19

    7

     原代码:

     1 #include <stdio.h>
     2 int prime(int x)        //判断是否为素数
     3 {    
     4     int i;
     5 
     6     if (x==1)
     7         return 0;
     8 
     9     for(i=2;i<x;i++)
    10     {
    11         if(!(x%i))
    12             return 0;
    13     }
    14 
    15     return 1;
    16 }
    17 
    18 int main()
    19 {
    20     int i,j,n,a[6],temp1,temp2;
    21     int prime(int x);
    22 
    23     //printf("n=");
    24     scanf("%d",&n);
    25 
    26     for (i=0;i<n;i++)    //输入相应数值
    27     {
    28         scanf("%d",&a[i]);
    29     }
    30 
    31     for(i=0;i<n;i++)
    32     {
    33         
    34         temp1=(temp2=a[i]);
    35         for(j=a[i];;j++)        //求出大于等于数值的素数
    36         {
    37             if(prime(j))
    38             {
    39                 temp1=j;
    40                 break;
    41             }
    42         }
    43 
    44         for(j=a[i]-1;;j--)        //求出小于数值的素数
    45         {
    46             if(prime(j))
    47             {
    48                 temp2=j;
    49                 break;
    50             }
    51         }
    52 
    53         printf("%d
    ",temp1-a[i]<=a[i]-temp2?temp1:temp2);
    54     }
    55     
    56     return 0;
    57 }

     评析:

        int i,j,n,a[6],temp1,temp2;
        int prime(int x);

       老问题,变量太多,数据结构设计不合理,函数类型定义位置不当。实际上这种问题有一个以不变应万变的“标准”句型:

    #include <stdio.h>
    
    int main( void )
    {
    
       int n ;
    
       scanf("%d" , &n);
       
       while ( n -- > 0 )
       {
         //定义解决问题需要的变量
          //输入测试数据
          //解决问题
       }
       
       return 0;
    }

       其中,裸体的“scanf("%d" , &n);”只在这种特定情况(刷题)下可以接受,对于一般情况,在输入前给用户一个提示信息为好。

        for (i=0;i<n;i++)    //输入相应数值
        {
            scanf("%d",&a[i]);
        }
    
        for(i=0;i<n;i++)
        {
            
            temp1=(temp2=a[i]);
            for(j=a[i];;j++)        //求出大于等于数值的素数
            {
                if(prime(j))
                {
                    temp1=j;
                    break;
                }
            }
    
            for(j=a[i]-1;;j--)        //求出小于数值的素数
            {
                if(prime(j))
                {
                    temp2=j;
                    break;
                }
            }
    
            printf("%d
    ",temp1-a[i]<=a[i]-temp2?temp1:temp2);
        }

       显然应该合并为一个for语句。原作者大概误以为必须全部输入之后才能开始解决问题,估计是题目对输入输出样式的说明容易让人误解,实际上输入与输出是相互独立的。不必拘泥于先全部输入后再开始逐个解决问题。 

            for(j=a[i];;j++)        //求出大于等于数值的素数
            {
                if(prime(j))
                {
                    temp1=j;
                    break;
                }
            }
    
            for(j=a[i]-1;;j--)        //求出小于数值的素数
            {
                if(prime(j))
                {
                    temp2=j;
                    break;
                }
            }

       这里存在几方面的问题,下面逐个评述。

      首先,抛开具体构思,仅仅从代码形式上来说这中写法就是不能容忍的。

      代码写得一定要“拽”(DRY)(参见代码写得要"拽"(DRY)——《C解毒》试读)。

      而这里的两条for语句,本质上是一模一样的。可以说,一个是对另一个的复制粘贴(如果写的时候连复制粘贴都没用到,而是老老实实一个字一个写成的,那就更成问题了。那说明连计算机都不会用)。

      所以无论如何这两条语句都应该想到用函数实现: 

    temp1 = find_prime(a[i],1);   //求从a[i]开始的第一个素数(步长为1);
    temp2 = find_prime(a[i]-1,-1);//求从a[i]-1开始的第一个素数(步长为-1);

       这样写代码要漂亮多了。

      再说一下逻辑上的缺陷。

      “大于等于”的素数求出之后,如果是“等于”,那么问题已经得解,就没必要执行第二条循环语句了,所以应该跳过第二条循环语句。 

        for( i = 0 ; i < n ; printf("%d
    ",solve) , i++ )
        {
            for(j=a[i];;j++)        //求出大于等于数值的素数
            {
                      //……
            }
    
           if ( temp1 == a[i] )
           {
               //输出该素数(temp1或a[i])
               continue ;                  //转到计算下一个a[i]
           }
    
            for(j=a[i]-1;;j--)        //求出小于数值的素数
            {
                     //……
            }
           printf("%d
    ",temp1-a[i]<=a[i]-temp2?temp1:temp2);
        }

       如果用函数求下一个素数,代码还可以写得更漂亮一些:

        for(i=0;i<n;i++)
        {
            int temp1 , temp2 ;
            
            if ( (temp1 = find_prime(a[i],1)) == a[i] )
            {
               printf("%d
    ",temp1);//输出temp1
               continue ; 
            }
          
            temp2 = find_prime(a[i]-1,-1)
    
            printf("%d
    ",temp1-a[i]<=a[i]-temp2?temp1:temp2);
        }

       这显然要比原来的代码干净整洁多了。如果觉得两条printf()调用不好,还可以这样写(又是逗号表达式,呵呵):

        for( i = 0 ; i < n ; printf("%d
    ",solve) , i++ )
        {
            int temp1 , temp2 , solve ;
            
            
            if ( (temp1 = find_prime(a[i],1)) == a[i] )
            {
               solve = temp1 ;
               continue ; 
            }
          
            temp2 = find_prime(a[i]-1,-1)
            solve = temp1-a[i]<=a[i]-temp2?temp1:temp2 ;
        }

       此外要说一下的是,无论是在main()中还是在prime()函数中,作者都没有认真考虑当输入整数并不处于两个素数之间的情况。事实上这是有可能的。比如输入为1,那么找小于1的素数是不可能的。但在原代码中,明显能看出作者根本就没意识到这件事(main()中没有相关的处理,prime()函数没有对0和负整数进行判断)。这应该说是原代码中的一个BUG。

      最后再说一下总体思路方面的问题。      

      作者的思路是先“求出大于等于数值的素数”,再“求出小于数值的素数”。这个思路可行,但不够好。比较好的思考应该是:

    1. 看这个数(假定是X)是不是素数
    2. 如不是,判断X+1是不是素数
    3. 如不是,判断X-1是不是素数
    4. 如不是,判断X+2是不是素数
    5. 如不是,判断X-2是不是素数
    6. ……

      这种想法更自然,一旦找到素数就完活儿,而不会做无用功。

      而原作者的代码则存在这样的可能:先求出一个比X大很多的素数,但其实解是X-1;或者求出大于X的素数是X+1,又去找出一个比X小很多的素数。这两种情况下,计算机总要做一些很无聊且无意义的工作。

      当然,原代码还可以继续改进,但在错误的总体思路指导下,改进的空间极其狭仄。即使改了,也无法彻底根除指令雍余的问题。

      而按照下面次序寻找最近素数

      X X+1 X-1 X+2 X-2 X+3……

      则不存在类似问题。

      不难发现,这个序列相邻两项差的绝对值,恰好构成自然数列:1 2 3 4 5 ……

      据此,重构如下: 

    重构:

    /*
    问题: 
    素数 
    在世博园某信息通信馆中,游客可利用手机等终端参与互动小游戏,与虚拟人物Kr. Kong 进行猜数比赛。
    当屏幕出现一个整数X时,若你能比Kr. Kong更快的发出最接近它的素数答案,你将会获得一个意想不到的礼物。 
    
    例如:当屏幕出现22时,你的回答应是23;当屏幕出现8时,你的回答应是7;
    若X本身是素数,则回答X;若最接近X的素数有两个时,则回答大于它的素数。 
    
    输入:第一行:N 要竞猜的整数个数 
    接下来有N行,每行有一个正整数X 
    输出:输出有N行,每行是对应X的最接近它的素数 
    
    样例:输入 
    4 
    22 
    5 
    18 
    8 
    输出 
    23 
    5 
    19 
    7
    
    作者:薛非
    出处:http://www.cnblogs.com/pmer/   “C语言初学者代码中的常见错误与瑕疵”系列博文 
    */
    
    #include <stdio.h>
    #include <stdbool.h>
    
    int get_nearest( int);
    bool be_prime( int );
    
    int main( void )
    {
    
       unsigned n ;
       
       puts( "数据组数=?" );
       scanf("%u" , &n); //这里没写输入提示 
       
       while ( n -- > 0u )
       {
          int x ; 
          
          scanf("%d" , & x);
          printf("%d
    " , get_nearest( x ) );
       }   
    
       return 0;
    }
    
    int get_nearest( int x )
    {
       int n , s ;//步长增量,符号
        
       for ( n = 0 , s = -1 ; ! be_prime( x ) ; n ++ , s = -s , x += s * n )
       {
       }
    
       return x ;
    }
        
    bool be_prime( int x )
    {
       int fac ; 
       
       if ( x <= 1 )
          return false ;
           
       for ( fac = 2 ; fac * fac <= x ; fac ++ )
          if ( x % fac == 0 )
             return false ;
       
       return true ;
    }

    不足:

      重构的代码中,be_prime()函数,仅仅从功能角度来说并没有什么问题。但若放在问题的背景下,它的效率太低了。这个函数需要反复地对每一个x都进行for ( fac = 2u ; fac * fac <= x ; fac ++ )这样的循环判断,如果问题要求判断的只有一个整数,这种写法也许无可厚非。但问题要求判断的是X X+1 X-1 X+2 X-2 X+3……这样一个序列,这样的写法就非常笨拙了。但是若想进一步改进却也不那么容易。这次就不改了,我将在后续的博文中给出改进的写法。

  • 相关阅读:
    模拟登陆江西理工大学教务系统
    python3爬虫 -----华东交大校园新闻爬取与数据分析
    以selenium模拟登陆12306
    PAT (Basic Level) Practice (中文)1076 Wifi密码 (15 分)
    PAT (Basic Level) Practice (中文)1047 编程团体赛 (20 分)
    PAT (Basic Level) Practice (中文)1029 旧键盘 (20 分)
    PAT (Basic Level) Practice (中文)1016 部分A+B (15 分)
    PAT (Basic Level) Practice (中文)1031 查验身份证 (15 分)
    PAT (Basic Level) Practice (中文)1041 考试座位号 (15 分)
    PAT (Basic Level) Practice (中文)1037 在霍格沃茨找零钱 (20 分)
  • 原文地址:https://www.cnblogs.com/pmer/p/3434478.html
Copyright © 2011-2022 走看看