帮初学者改代码——playerc之“练习:求完数问题”(上)
原文:“练习:求完数问题”
原代码:
//
#include <stdio.h>
#include <stdlib.h>
#include <math.h> #define DIVISERS_MAX_LENGTH (1024)
#define TOP (10000) int main(void)
{
/*
* 储存因子,成对的储存。例如6
* 1,6, 2,3
*/
int divisers[DIVISERS_MAX_LENGTH] = {0};
int divisers_count = 0; /* 要判断的数 */
int number;
int sum; /* 指定 i的最大值, */
int i_max;
int i ;
int diviser; for(number = 2; number < (TOP + 1) ; ++number ){
/* 所有数可以被 1 和 自己整除,所以,下面for 从 2 开始
sum = 1;
count = 2;
i = 2;
i_max = number /2
*/
divisers[0] = 1;
divisers[1] = number; /* 关于 i < i_max .
因为整除啊,
m = i / n
如果 i 能被 n 整除,则,n 最小,m最大。
随着循环进行 最小和最大不断被求出来,所以超过最大的就不用再算了。 其实,这个地方使用 i < sqrt(number) +1 最好了。
*/
for (divisers_count = 2,i = 2,sum = 1,i_max = sqrt(number)+1; i < i_max; ++i){
if (!(number % i)){
if (!(divisers_count < DIVISERS_MAX_LENGTH)){
fprintf(stderr, "Too many divisers\n number:%d count:%d\n",number,divisers_count);
break;
}
divisers[divisers_count] = i;
diviser = number/i; sum += i;
++ divisers_count;
if (diviser != i) {
divisers[divisers_count] = diviser;
sum += diviser;
++ divisers_count;
} if (sum > number) break;
}
} /* 下面是输出 */
if (sum == number){
printf("%d ,Its factors are : ", number);
for (i =0; i < divisers_count ; i += 2 ){
printf ("%d ",divisers[i]);
}
/*
这个因为是倒叙输出,数组可能不是偶数,所以要判断开始的位置
i的开始的位置应该是:
i = (count -1 )- ( -((count-1) -(i-2)) +1 )
化简后
i = 2*count -i -1;
*/
for (i = 2*divisers_count -i -1; i > 2; i-=2 ){
printf("%d ",divisers[i]);
}
printf("\n");
}
}
/* 结束了。。。*/
printf("end\n");
}
总体来看,代码的思路还是比较清晰的:列举出待求区间内所有正整数,然后逐个判断是否是完数。
作者自称是“贪心算法”,这个说法有待商榷。因为“贪心算法”不一定能得到解。作者所采用的算法其实一种优化手法,一旦发现真因子和大于该正整数,就及时停止( if
(sum > number)
break
;
),转入下一个数的判断。
总体上的缺点主要有,main()函数内第一个层次中的变量太多,整个代码只有一个main()函数。把代码改成下面的样子会更清晰:
int main( void )
{
int number;/* 要判断的数 */ for( number = 2 ; number < ( TOP + 1 ) ; ++number ){
//判断number是否为完数
//如是,输出
}
return 0;
}
下面谈细节问题:
#define DIVISERS_MAX_LENGTH (1024)
#define TOP (10000)
作者用TOP规定求解范围的上限。DIVISERS_MAX_LENGTH为因子数组的尺寸。
从逻辑上讲,这两条预处理命令的次序应该颠倒一下,因为因子数组的尺寸是根据TOP确定的。
1024这个尺寸太大了,实际上用不到这么大的数组。作者是发现数组尺寸定为12不够,20又不够,最后索性定为1024的。虽然定了这么大的尺寸,可是还是不放心,在代码中又写了数组尺寸不够时的处理代码。这些代码其实是不必要的。与其花功夫写这些不必要的代码,其实不如花工夫事先认真地估算一下所需要数组的大小。
不大于10000的正整数,因子不超过64个,这是我的估计。理由如下:
一个数因子数的个数,取决于这个数素因子的个数,素因子越多,因子也越多。例如:
30 = 2 * 3 * 5
有1 2 3 5 6 10 15 30一共8个因子。而32 = 2 * 2 * 2 * 2 * 2 ,虽然比30大,却只有
1 2 4 8 16 32一共6个因子。
因为
10000/2/3/5/7/11 ≈ 4.33
所以我断定不大于10000的正整数中,因子数最多的应该是2^3*3*5*7*11 = 9240,它一共有(3+1)×(1+1)×(1+1)×(1+1)×(1+1)= 64 个因子。
再来看一下main()函数:
main()函数中,除了变量位置不恰当以外,变量太多以及所有的事情都在main()一个函数内完成也严重影响代码质量。这两者其实都是因为代码没有从一个较高的高度上首先概括性的思考解决问题,而是一开始就纠结到细节当中了,这很不可取。代码应该这样写:
#define TOP (10000)
#define DIVISERS_MAX_LENGTH ((3+1)*(1+1)*(1+1)*(1+1)*(1+1)) int main( void )
{
int number;/* 要判断的数 */ for( number = 2 ; number < ( TOP + 1 ) ; ++number ){
int divisers[DIVISERS_MAX_LENGTH] = { 0 , number } ; if ( number是完数 ){
输出
}
} return 0;
}
这种写法,逻辑上清清楚楚,无懈可击。
由此可见,main()函数其实不需要那么多变量。
写代码时,要处理的数据越多,变量应该定义得越少。如果处理的数据不多,就更不应该定义较多的变量。定义变量一定要遵循一个原则,只有非定义不可的时候才定义变量。
就这个写法而言,main()里最多只应该定义两个变量。在for语句中必须定义divisers数组的理由是判断“number是完数”以及“输出”需要这个数组。
下面是初步修改后的代码:
#include <stdio.h>
#include <math.h>
#include <stdbool.h> bool be_ferfect( int , int [] , int );
void output( int , int [] , int ); #define TOP (10000)
#define DIVISERS_MAX_LENGTH ((3+1)*(1+1)*(1+1)*(1+1)*(1+1)) int main( void )
{
int number;/* 要判断的数 */ for( number = 2 ; number < ( TOP + 1 ) ; ++number ){ int divisers[DIVISERS_MAX_LENGTH] = { 1 } ;//{ 1 , number } ; if ( be_ferfect( number , divisers , DIVISERS_MAX_LENGTH ) == true ){
output ( number , divisers , DIVISERS_MAX_LENGTH ) ;
}
} return 0;
} bool be_ferfect( int number , int divisers[] , int size )
{
int sum = 1 ;
int i = 2 ;
int i_max ;
int divisers_count = 2 ;
int diviser; for ( i = 2 , i_max = sqrt(number)+1 ; i < i_max ; ++ i ){
if (!(number % i)){ divisers[divisers_count ++ ] = i;
sum += i; diviser = number / i ;
if ( diviser != i ) {
divisers[ divisers_count ++ ] = diviser;
sum += diviser;
} if (sum > number)
return false;
}
} return sum == number ;
} void output ( int number , int divisers[] , int size )
{
int i ;
printf("%d ,Its factors are : ", number);
for ( i = 0 ; i < size ; i += 2 ){
if ( divisers[i] == 0 ){
i -= 1 ; //i -= 2;
break ;
}
printf ("%d ",divisers[i]);
} if ( divisers[i] == 0 ){
i -=2 ;
}
//divisers[i] == 0 ? i -- : i ++ ; while ( i > 1 ){
printf("%d ",divisers[i]);
i -= 2;
}
putchar('\n');
}
尽管还有很多毛病,但应该是比原来好多了。至少main()写得清清楚楚,明明白白。
续文链接:帮初学者改代码——playerc之“练习:求完数问题”(下)
帮初学者改代码——playerc之“练习:求完数问题”(上)的更多相关文章
- 帮初学者改代码——playerc之“练习:求完数问题”(下)
前文链接:帮初学者改代码——playerc之“练习:求完数问题”(上) 再来看看be_ferfect()应该如何改. be_ferfect()函数的功能是判断number是否为完数,同时把因子对写入d ...
- 帮初学者改代码——有多少青春可以挥霍之“c语言 多重排序”
原文:“c语言 多重排序” 原代码: #include<stdio.h> #include<string.h> struct A { char name[100]; int g ...
- 蓝桥杯 算法训练 ALGO-152 8-2求完数
算法训练 8-2求完数 时间限制:50.0s 内存限制:256.0MB 问题描述 如果一个自然数的所有小于自身的因子之和等于该数,则称为完数.设计算法,打印1-9999之间的所有完数. 样例 ...
- openmp 并行求完数
// GetWanShu.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h" #include "omp.h" #inclu ...
- Java50道经典习题-程序9 求完数
题目:一个数如果恰好等于它的因子之和,这个数就称为"完数".例如6=1+2+3.编程找出1000以内的所有完数. public class Prog9 { public stati ...
- JAVA 基础编程练习题9 【程序 9 求完数】
9 [程序 9 求完数] 题目:一个数如果恰好等于它的因子之和,这个数就称为"完数".例如 6=1+2+3.编程找出 1000 以内的 所有完数. package cskaoyan ...
- Java实现 蓝桥杯VIP 算法训练 求完数
问题描述 如果一个自然数的所有小于自身的因子之和等于该数,则称为完数.设计算法,打印1-9999之间的所有完数. 样例输出 与上面的样例输入对应的输出. 例: 数据规模和约定 1-9999 publi ...
- ALGO-152_蓝桥杯_算法训练_8-2求完数
记: 掌握完数的概念 AC代码: #include <stdio.h> int main(void) { int i,j,sum; ; i <= ; i ++) { sum = ; ...
- OpenMP求完数
源代码: #include "stdafx.h" //必须写在首行,因为其前面的include都会被忽略 #include "omp.h" #include & ...
随机推荐
- 使用git Rebase让历史变得清晰
当多人协作开发一个分支时,历史记录通常如下方左图所示,比较凌乱.如果希望能像右图那样呈线性提交,就需要学习git rebase的用法. “Merge branch”提交的产生 我们的工作流程是:修改代 ...
- linux关机
init 0; init是所有进程的祖先﹐它的进程号始终为1﹐所以发送TERM信号给init会终止所有的用户进程﹑守护进程等.shutdown 就是使用这种机制.init定义了8个运行级别(runle ...
- Selenium2学习-035-WebUI自动化实战实例-033-页面快照截图应用之三 -- 区域截图(专业版)
之前有写过两篇博文讲述了 WebUI 自动化测试脚本中常用的截图方法,敬请参阅如下所示链接: 浏览器显示区域截图 浏览器指定区域截图 那么当需要截取的区域不在浏览器显示窗口范围之内时,之前的方法显然无 ...
- 生成uid的算法
private function _getUid() { //2013-01-01 00:00:00 (timestamp-microtime) $startTime= 1356969600000; ...
- Javascript对象、Jquery扩展简单应用
Javascript对象,表现方式一: person = new Object(); person.firstname = "An"; person.lastname = &quo ...
- javascript知识点记录(1)
javascript一些知识点记录 1.substring,slice,substr的用法 substring 和slice 都有startIndex 和 endIndex(不包括endInex),区 ...
- C#中static静态变量的用法
使用 static 修饰符声明属于类型本身而不是属于特定对象的静态成员static修饰符可用于类.字段.方法.属性.运算符.事件和构造函数,但不能用于索引器.析构函数或类以外的类型 静态全局变量 定义 ...
- CFBundleVersion与CFBundleShortVersionString
CFBundleVersion,标识(发布或未发布)的内部版本号.这是一个单调增加的字符串,包括一个或多个时期分隔的整数. CFBundleShortVersionString 标识应用程序的发布版 ...
- Docker数据管理
用户在使用Docker的过程中,往往需要能查看容器内应用产生的数据,或者需要把容器内的数据进行备份,甚至多个容器之间进行数据共享,这必然涉及到Docker的数据管理. 容器中管理数据主要有两种方式: ...
- eclipse启动tomcat错误:A Java Exception has occurred(转)
在tomcat bin目录下执行startup.bat可以正常启动,但在eclipse下安装了tomcat插件并且配置tomcat路径后启动且报错:A Java Exception has occur ...