问题:

  问题出处见 C语言初学者代码中的常见错误与瑕疵(5) 。

  在该文的最后,曾提到完成的代码还有进一步改进的余地。本文完成了这个改进。所以本文讨论的并不是初学者代码中的常见错误与瑕疵,而是对我自己代码的改进和优化。标题只是为了保持系列的连续性。

改进

  程序的总体思想没有改变,所以main()函数不需要任何改动。

int main( void )
{
   unsigned n ;

   puts( "数据组数=?" );
   scanf( "%u" , &n ); 

    )
   {
      int x ; 

      puts( "整数X=?" );
      scanf( "%d", & x );

      printf("%d\n" , get_nearest( x ) ); //求最接近x的素数
   }   

   ;
}

  进一步的改进体现在

typedef
struct prime_list
   {
      unsigned prime;
      struct prime_list * next;
   }
Node;

int get_nearest( int x )
{
    ;         //步长增量
   ;         //符号
   Node * head = NULL ;   //素数链表 

   while ( ! be_prime( x , & head ) )
      x += ( sign = - sign ) * ++ step ;

   my_free(head) ;
   return x ;
}

  这里增加了一个链表head,用于存储素数表。这样,在判断素数时只用较小的素数试除就可以了,这可以使计算数量大为减少。因为与自然数相比,素数的数量很少(≈ln n / n 个)。此外,在判断完x是否为素数之后,如果需要判断下一个数(x += ( sign = - sign ) * ++ step ;)是否为素数,这个素数表还可以重复使用,最多再向其中添加一个素数就可以了。(注意最初素数表是空的)

  判断素数的方法很简单,小学生都懂。

bool be_prime( int x , Node * * pp ) //根据素数表pp判断x是否为素数
{
    )
      return false ;

    )
      return true ;

    ) // x对素数表pp中素数逐个求余有0值
      return false ;

   return true ;
}

  但是由于素数表(*pp==NULL)可能是空的,因此

int get_remainder( int x , Node * * pp )//x对素数表pp中素数逐个求余
{
   while ( * pp == NULL || sqr_less( (*pp) -> prime , x ) )//表中素数个数不足
      add_1_prime ( pp ) ;                                 //表中增加一个素数 

   Node * p = * pp ;

   while ( p != NULL )
   {
       )
         ;
      p = p -> next ;
   }

    ;
}

bool sqr_less ( int n , int x )
{
   return n * n < x ;
}

  需要先向其中添加素数

  add_1_prime ( pp ) ;

  直到

  sqr_less( (*pp) -> prime , x )

  依从小到大次序最后加入的那个素数的平方不小于x为止。

  对于

void add_1_prime( Node * * pp )
{
   if ( * pp == NULL )
   {
       add (  , pp );                     //第一个素数
       return ;
   }

    ;     //从最后一个素数之后开始找下一个素数 

   while ( !be_prime( next_p , pp ) )
      next_p ++ ;

   add( next_p , pp );                    //将下一个素数加入素数表
}

来说,加入第一个素数——2很容易,但是寻找素数表中最大素数后的下一个素数时,却需要判断一个整数是否是素数

be_prime( next_p , pp )

  这样,就会发现,这个过程最初是由判断某个数x是否是素数开始,

be_prime( x , & head )

  在判断过程中需要建立素数表,

add_1_prime ( pp ) ; 

  而建立素数表,又需要判断某个数是否是素数

be_prime( next_p , pp )

  这样就形成了一个极其复杂的间接递归调用。更为复杂的是,在调用的过程中,素数表本身即不断地被使用,而自身也处于不断的变化状态之中,即不断地被添加进新的素数,与复杂的间接递归一道,构成了比复杂更复杂的复杂的代码结构与复杂的数据结构的复杂的结合体。有兴趣的话可以自己算一下圈复杂度,如此复杂的情况通常并不容易遇到。

  这种局面完全是由于精打细算造成的,由于对速度的斤斤计较,从而形成了一幅小猫在拼命咬自己尾巴同时小猫自己又在不断变化的复杂无比的动态画面。由此我们不难理解,为什么有人说,“不成熟的优化是万恶之源”(Premature optimization is the root of all evil!- Donald Knuth)。因为优化往往意味着引人复杂。复杂也是一种成本,而且是一种很昂贵的成本。

  就这个题目而言这种成本应该算是值得,因为对于求一个较大的最接近的素数问题而言(例如对于109这个量级),两套代码的速度有天壤之别。

增强可读性?

  如果把建立素数表的要求写在get_nearest()函数中,可能会使代码可读性变得更好些。

int get_nearest( int x )
{
    ;         //步长增量
   ;         //符号
   Node * head = NULL ;   //素数链表 

   while ( 建立最大素数平方不小于x的素数表() , ! be_prime( x , & head ) )
      x += ( sign = - sign ) * ++ step ;

   my_free(head) ;
   return x ;
}

  但这里的这个这个“,”是免不掉的,且圈复杂度不变。

  至于这种写法是否真的改善了可读性,恐怕是见仁见智。

进一步提高效率

  没什么更好的办法,只能用点“赖皮”手段,即充分运用已有的素数知识,帮计算机算出一部分素数。

void add_1_prime( Node * * pp )
{
   if ( * pp == NULL )
   {
       add (  , pp );                     //第一个素数
       return ;
   }

   switch ( ( * pp ) -> prime )
   {
      : add (   , pp );
               return ;
      : add (   , pp );
               return ;
      /* 这里可以依样写多个case,只要是按照素数从小到大的次序*/
      default:
                {
                    ;     //从最后一个素数之后开始找下一个素数 

                   while ( !be_prime( next_p , pp ) )
                       next_p ++ ;

                   add( next_p , pp );                    //将下一个素数加入素数表
                   return ;
                }
   }
}

  这里switch语句的结构非常有趣。

重构

/*
问题:
素数
在世博园某信息通信馆中,游客可利用手机等终端参与互动小游戏,与虚拟人物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语言初学者代码中的常见错误与瑕疵”系列博文

版本:V 2.1
*/

#include <stdio.h>
#include <stdbool.h>

typedef
struct prime_list
   {
      unsigned prime;
      struct prime_list * next;
   }
Node;

int get_nearest( int );
bool be_prime( int , Node * * );
int get_remainder( int , Node * * ) ;
void add_1_prime( Node * * );
bool sqr_less ( int , int );
void add ( int , Node * * );
void my_malloc( Node * *  );
void my_free( Node * );

int main( void )
{
   unsigned n ;

   puts( "数据组数=?" );
   scanf( "%u" , &n ); 

    )
   {
      int x ; 

      puts( "整数X=?" );
      scanf( "%d", & x );

      printf("%d\n" , get_nearest( x ) ); //求最接近x的素数
   }   

   ;
}

int get_nearest( int x )
{
    ;         //步长增量
   ;         //符号
   Node * head = NULL ;   //素数链表 

   while ( ! be_prime( x , & head ) )
      x += ( sign = - sign ) * ++ step ;

   my_free(head) ;
   return x ;
}

bool be_prime( int x , Node * * pp ) //根据素数表pp判断x是否为素数
{
    )
      return false ;

    )
      return true ;

    ) // x对素数表pp中素数逐个求余有0值
      return false ;

   return true ;
}

int get_remainder( int x , Node * * pp )//x对素数表pp中素数逐个求余
{
   while ( * pp == NULL || sqr_less( (*pp) -> prime , x ) )//表中素数个数不足
      add_1_prime ( pp ) ;                                 //表中增加一个素数 

   Node * p = * pp ;

   while ( p != NULL )
   {
       )
         ;
      p = p -> next ;
   }

    ;
}

bool sqr_less ( int n , int x )
{
   return n * n < x ;
}

//“偷奸耍滑”的add_1_prime()
void add_1_prime( Node * * pp )
{
   if ( * pp == NULL )
   {
       add (  , pp );                     //第一个素数
       return ;
   }

   switch ( ( * pp ) -> prime )
   {
      : add (   , pp );
               return ;
      : add (   , pp );
               return ;
      /* 这里可以依样写多个case,只要是按照素数从小到大的次序*/
      default:
                {
                    ;     //从最后一个素数之后开始找下一个素数 

                   while ( !be_prime( next_p , pp ) )
                          next_p ++ ;

                   add( next_p , pp );                    //将下一个素数加入素数表
                   return ;
                }
   }
}

//老老实实的add_1_prime()
//void add_1_prime( Node * * pp )
//{
//   if ( * pp == NULL )
//   {
//       add ( 2 , pp );                     //第一个素数
//       return ;
//   }
//
//   int next_p = ( * pp )->prime + 1 ;     //从最后一个素数之后开始找下一个素数
//
//   while ( !be_prime( next_p , pp ) )
//      next_p ++ ;
//
//   add( next_p , pp );                    //将下一个素数加入素数表
//}

void add ( int prime , Node * * pp )
{
   Node * temp ;

   my_malloc( & temp );
   temp -> prime = prime ;
   temp -> next = * pp ;
   * pp = temp ;
}

void my_malloc( Node * * p_p )
{
   if ( ( * p_p = malloc( sizeof (* * p_p) ) ) == NULL )
      exit();
}

void my_free( Node * p )
{
   Node * temp ;
   while ( ( temp = p ) != NULL )
   {
      p = p->next;
      free( temp );
   }
}

相关博客:

  偶然发现Jingle Guo网友后来研究同一问题的一篇博文,我感觉对阅读此文的网友可能有一定的参考价值,故在此给出相关链接:从关于素数的算法题来学习如何提高代码效率

一个超复杂的间接递归——C语言初学者代码中的常见错误与瑕疵(6)的更多相关文章

  1. C语言初学者代码中的常见错误与瑕疵(5)

    问题: 素数 在世博园某信息通信馆中,游客可利用手机等终端参与互动小游戏,与虚拟人物Kr. Kong 进行猜数比赛. 当屏幕出现一个整数X时,若你能比Kr. Kong更快的发出最接近它的素数答案,你将 ...

  2. 分数的加减法——C语言初学者代码中的常见错误与瑕疵(12)

    前文链接:分数的加减法——C语言初学者代码中的常见错误与瑕疵(11) 重构 题目的修正 我抛弃了原题中“其中a, b, c, d是一个0-9的整数”这样的前提条件,因为这种限制毫无必要.只假设a, b ...

  3. C语言初学者代码中的常见错误与瑕疵(9)

    题目 字母的个数 现在给你一个由小写字母组成字符串,要你找出字符串中出现次数最多的字母,如果出现次数最多字母有多个那么输出最小的那个. 输入:第一行输入一个正整数T(0<T<25) 随后T ...

  4. 要心中有“数”——C语言初学者代码中的常见错误与瑕疵(8)

    在 C语言初学者代码中的常见错误与瑕疵(7) 中,我给出的重构代码中存在BUG.这个BUG是在飞鸟_Asuka网友指出“是不是时间复杂度比较大”,并说他“第一眼看到我就想把它当成一个数学问题来做”之后 ...

  5. C语言初学者代码中的常见错误与瑕疵(7)

    问题: 矩形的个数 在一个3*2的矩形中,可以找到6个1*1的矩形,4个2*1的矩形3个1*2的矩形,2个2*2的矩形,2个3*1的矩形和1个3*2的矩形,总共18个矩形.给出A,B,计算可以从中找到 ...

  6. C语言初学者代码中的常见错误与瑕疵(23)

    见:C语言初学者代码中的常见错误与瑕疵(23)

  7. C语言初学者代码中的常见错误与瑕疵(19)

    见:C语言初学者代码中的常见错误与瑕疵(19)

  8. C语言初学者代码中的常见错误与瑕疵(14)

    见:C语言初学者代码中的常见错误与瑕疵(14) 相关链接:http://www.anycodex.com/blog/?p=87

  9. C语言初学者代码中的常见错误与瑕疵(1)

    曾在豆瓣上看到过一个小朋友贴出他自己的代码(http://www.douban.com/group/topic/40293109/),当时随口指点了几句.难得这位小朋友虚心修正.从善如流,不断地改,又 ...

随机推荐

  1. dirname 命令

    dirname 命令用途将指定路径除了最后以外的部分写到标准输出.语法dirname Path描述dirname 命令读取指定路径名保留最后一个"/"(斜杠)及其后面的字符,删除其 ...

  2. jquery 使用方法(二)

    jquery語法: jquery語法是為html元素的选取编制的,可以对元素执行某些操作. 基础语法是:$(selector).action() 美元符号定义 jQuery 选择符(selector) ...

  3. 【hadoop2.6.0】利用Hadoop的 Java API

    Hadoop2.6.0的所有Java API都在 http://hadoop.apache.org/docs/r2.6.0/api/overview-summary.html 里. 下面实现一个利用J ...

  4. 如何给xml应用样式

    引子:可扩展标记语言xml(Extensible Markup Language)自己平常也用到的不多,除了在ajax处理服务器返回的数据可能会用到外(不过一般用json处理数据的比较常见)还真没怎么 ...

  5. TDirectory.GetLogicalDrives获取本地逻辑驱动器

    使用函数: System.IOUtils.TDirectory.GetLogicalDrives class function GetLogicalDrives: TStringDynArray; s ...

  6. ios中block中的探究

    http://blog.csdn.net/jasonblog/article/details/7756763

  7. Unity3D-Shader-复古电影荧幕特效

    [旧博客转移 - 2015年12月6日 18:12]   今天用Shader做了一个复古荧幕效果,老电视机放映的感觉,写篇文章记录一下     原始图片:   没错,这就是电影<泰坦尼克号> ...

  8. 什么是命名空间?php命名空间的基本应用分享

    什么是命名空间? php中声明的函数名.类名和常量的名称,在同一次运行中是不能重复的,否则会产生一个致命的错误,常见的解决方法是约定一个前缀.例如 ,在项目开发时,用户 User 模块中的控制器和数据 ...

  9. 浅析foreach语句

    本篇是我对于foreach语句(增强for)的总结: 我的总结流程如下: 1.先整体说明增强for遍历集合与数组之间的区别. 2.通过一维数组来说明(给出反编译的源码,形成对照). 3.通过二维数组来 ...

  10. Python-Django-Ajax

    什么是Ajax: 通过js语言跟后台进行交互的一个东西 -特点:异步,局部刷新 ajax往后台提交数据 $.ajax({ url:'请求的地址', type:'get/post', data:{key ...