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……这样一个序列,这样的写法就非常笨拙了。但是若想进一步改进却也不那么容易。这次就不改了,我将在后续的博文中给出改进的写法。

  • 相关阅读:
    用SecureCRT 查看Linux下日志的简单命令
    性能测试知多少---并发用户
    性能测试指标的基本概念
    软件测试基本理论
    Selenium 入门视频
    零基础学软件测试
    装饰器作业
    <python全栈开发基础>学习过程笔记【16d】装饰器(含time模块)
    【搬家】我的CSDN博客地址http://my.csdn.net/myloveprogrmming
    《Python全栈开发》学习过程笔记【3】
  • 原文地址:https://www.cnblogs.com/pmer/p/3434478.html
Copyright © 2011-2022 走看看