一个超复杂的间接递归——C语言初学者代码中的常见错误与瑕疵(6)
问题:
问题出处见 C语言初学者代码中的常见错误与瑕疵(5) 。
在该文的最后,曾提到完成的代码还有进一步改进的余地。本文完成了这个改进。所以本文讨论的并不是初学者代码中的常见错误与瑕疵,而是对我自己代码的改进和优化。标题只是为了保持系列的连续性。
改进
程序的总体思想没有改变,所以main()函数不需要任何改动。
int main( void )
{
unsigned n ; puts( "数据组数=?" );
scanf( "%u" , &n ); while ( n -- > )
{
int x ; puts( "整数X=?" );
scanf( "%d", & x ); printf("%d\n" , get_nearest( x ) ); //求最接近x的素数
} return ;
}
进一步的改进体现在
typedef
struct prime_list
{
unsigned prime;
struct prime_list * next;
}
Node; int get_nearest( int x )
{
int step = ; //步长增量
int sign = -; //符号
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是否为素数
{
if ( x <= )
return false ; if ( x == )
return true ; if ( get_remainder( x , pp ) == ) // 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 )
{
if ( x % p -> prime == )
return ;
p = p -> next ;
} return ! ;
} 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 ;
} int next_p = ( * pp )->prime + ; //从最后一个素数之后开始找下一个素数 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 )
{
int step = ; //步长增量
int sign = -; //符号
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 )
{
case : add ( , pp );
return ;
case : add ( , pp );
return ;
/* 这里可以依样写多个case,只要是按照素数从小到大的次序*/
default:
{
int next_p = ( * pp )->prime + ; //从最后一个素数之后开始找下一个素数 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 ); while ( n -- > )
{
int x ; puts( "整数X=?" );
scanf( "%d", & x ); printf("%d\n" , get_nearest( x ) ); //求最接近x的素数
} return ;
} int get_nearest( int x )
{
int step = ; //步长增量
int sign = -; //符号
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是否为素数
{
if ( x <= )
return false ; if ( x == )
return true ; if ( get_remainder( x , pp ) == ) // 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 )
{
if ( x % p -> prime == )
return ;
p = p -> next ;
} return ! ;
} 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 )
{
case : add ( , pp );
return ;
case : add ( , pp );
return ;
/* 这里可以依样写多个case,只要是按照素数从小到大的次序*/
default:
{
int next_p = ( * pp )->prime + ; //从最后一个素数之后开始找下一个素数 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)的更多相关文章
- C语言初学者代码中的常见错误与瑕疵(5)
问题: 素数 在世博园某信息通信馆中,游客可利用手机等终端参与互动小游戏,与虚拟人物Kr. Kong 进行猜数比赛. 当屏幕出现一个整数X时,若你能比Kr. Kong更快的发出最接近它的素数答案,你将 ...
- 分数的加减法——C语言初学者代码中的常见错误与瑕疵(12)
前文链接:分数的加减法——C语言初学者代码中的常见错误与瑕疵(11) 重构 题目的修正 我抛弃了原题中“其中a, b, c, d是一个0-9的整数”这样的前提条件,因为这种限制毫无必要.只假设a, b ...
- C语言初学者代码中的常见错误与瑕疵(9)
题目 字母的个数 现在给你一个由小写字母组成字符串,要你找出字符串中出现次数最多的字母,如果出现次数最多字母有多个那么输出最小的那个. 输入:第一行输入一个正整数T(0<T<25) 随后T ...
- 要心中有“数”——C语言初学者代码中的常见错误与瑕疵(8)
在 C语言初学者代码中的常见错误与瑕疵(7) 中,我给出的重构代码中存在BUG.这个BUG是在飞鸟_Asuka网友指出“是不是时间复杂度比较大”,并说他“第一眼看到我就想把它当成一个数学问题来做”之后 ...
- 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,计算可以从中找到 ...
- C语言初学者代码中的常见错误与瑕疵(23)
见:C语言初学者代码中的常见错误与瑕疵(23)
- C语言初学者代码中的常见错误与瑕疵(19)
见:C语言初学者代码中的常见错误与瑕疵(19)
- C语言初学者代码中的常见错误与瑕疵(14)
见:C语言初学者代码中的常见错误与瑕疵(14) 相关链接:http://www.anycodex.com/blog/?p=87
- C语言初学者代码中的常见错误与瑕疵(1)
曾在豆瓣上看到过一个小朋友贴出他自己的代码(http://www.douban.com/group/topic/40293109/),当时随口指点了几句.难得这位小朋友虚心修正.从善如流,不断地改,又 ...
随机推荐
- UPW学习资料整理 .NET C# 转
开发工具下载https://www.visualstudio.com/?Wt.mc_id=DX_MVP5000319 Windows 10 UWP开发视频1http://blogs.windows.c ...
- ThroughRain第二次冲刺(每天更新
第二次冲刺时间: 11月28-12月5号 第一次冲刺目标及分配: 1. 查询点餐界面 认领:梁仕标 2. 链接数据库 认领:冯梓凡 3. 建立数据库的表 ...
- 软件工程---阅读《构建之法》P384~391
-阅读<构建之法>P384~391后,我充分认识到软件工程师的职业道德的重要性,具体有: 原则1:公众 原则2:客户与雇主 原则3:产品 原则4:判断 原则5:管理 原则6:职业 原则7: ...
- 极简Unity调用Android方法
简介 之前写了篇unity和Android交互的教程,由于代码里面有些公司的代码,导致很多网友看不懂,并且确实有点小复杂,这里弄一个极简的版本 步骤 废话不多说,直接来步骤吧 1.创建工程,弄大概像这 ...
- JAVA的网络编程基础概念
网络编程的目的就是指直接或间接地通过网络协议与其他计算机进行通讯.网络编程中有两个主要的问题,一个是如何准确的定位网络上一台或多台主机,另一个就是找到主机后如何可靠高效的进行数据传输.在TCP/IP协 ...
- 浅谈ImageList
ImageList组件用了很久,但是一直不太清楚它的实现原理,今天专门特意花了时间倒腾了下,终于弄明白了!于是在这里和大家分享下! 在设计页面中打卡工具箱-组件 找到ImageList组件,将它直接拖 ...
- [转载]Ubuntu14.04 LTS更新源
不同的网络状况连接以下源的速度不同, 建议在添加前手动验证以下源的连接速度(ping下就行),选择最快的源可以节省大批下载时间. 首先备份源列表: sudo cp /etc/apt/sources.l ...
- javascript url几种编码方式
1.escape() 不能直接用于URL编码,它的真正作用是返回一个字符的Unicode编码值.比如“春节”的返回结果是%u6625%u8282,escape()不对"+"编码主要 ...
- innerHTML和outerHTML有什么区别
一.区别:1)innerHTML: 从对象的起始位置到终止位置的全部内容,不包括Html标签.2)outerHTML: 除了包含innerHTML的全部内容外, 还包含对象标签本身. 二.例子1: & ...
- 机器学习实战 - 读书笔记(12) - 使用FP-growth算法来高效发现频繁项集
前言 最近在看Peter Harrington写的"机器学习实战",这是我的学习心得,这次是第12章 - 使用FP-growth算法来高效发现频繁项集. 基本概念 FP-growt ...