原文:“练习:求完数问题”

原代码:

//
#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之“练习:求完数问题”(上)的更多相关文章

  1. 帮初学者改代码——playerc之“练习:求完数问题”(下)

    前文链接:帮初学者改代码——playerc之“练习:求完数问题”(上) 再来看看be_ferfect()应该如何改. be_ferfect()函数的功能是判断number是否为完数,同时把因子对写入d ...

  2. 帮初学者改代码——有多少青春可以挥霍之“c语言 多重排序”

    原文:“c语言 多重排序” 原代码: #include<stdio.h> #include<string.h> struct A { char name[100]; int g ...

  3. 蓝桥杯 算法训练 ALGO-152 8-2求完数

     算法训练 8-2求完数   时间限制:50.0s   内存限制:256.0MB 问题描述 如果一个自然数的所有小于自身的因子之和等于该数,则称为完数.设计算法,打印1-9999之间的所有完数. 样例 ...

  4. openmp 并行求完数

    // GetWanShu.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h" #include "omp.h" #inclu ...

  5. Java50道经典习题-程序9 求完数

    题目:一个数如果恰好等于它的因子之和,这个数就称为"完数".例如6=1+2+3.编程找出1000以内的所有完数. public class Prog9 { public stati ...

  6. JAVA 基础编程练习题9 【程序 9 求完数】

    9 [程序 9 求完数] 题目:一个数如果恰好等于它的因子之和,这个数就称为"完数".例如 6=1+2+3.编程找出 1000 以内的 所有完数. package cskaoyan ...

  7. Java实现 蓝桥杯VIP 算法训练 求完数

    问题描述 如果一个自然数的所有小于自身的因子之和等于该数,则称为完数.设计算法,打印1-9999之间的所有完数. 样例输出 与上面的样例输入对应的输出. 例: 数据规模和约定 1-9999 publi ...

  8. ALGO-152_蓝桥杯_算法训练_8-2求完数

    记: 掌握完数的概念 AC代码: #include <stdio.h> int main(void) { int i,j,sum; ; i <= ; i ++) { sum = ; ...

  9. OpenMP求完数

    源代码: #include "stdafx.h" //必须写在首行,因为其前面的include都会被忽略 #include "omp.h" #include & ...

随机推荐

  1. C#连接数据库的四种方法(转)

    C#连接数据库的四种方法 在进行以下连接数据库之前,请先在本地安装好Oracle Client,同时本次测试System.Data的版本为:2.0.0.0. 在安装Oracle Client上请注意, ...

  2. chem02-- ajax登录

    1.ajaxLogin.jsp <%@ page language="java" contentType="text/html; charset=UTF-8&quo ...

  3. Javascript对象、Jquery扩展简单应用

    Javascript对象,表现方式一: person = new Object(); person.firstname = "An"; person.lastname = &quo ...

  4. LeetCode Number of Digit One

    原题链接在这里:https://leetcode.com/problems/number-of-digit-one/ 每10个数, 有一个个位是1, 每100个数, 有10个十位是1, 每1000个数 ...

  5. 每日目标——HTML 头部标签学习 2015-8-27

    <head> <title>bp</title> <meta http-equiv="Content-Type" content=&quo ...

  6. JAVA定时器实现之一(通过继承TimerTask)

    在某些时候, 我们需要实现这样的功能,某一程序隔一段时间执行一次,而这一事情由系统本身来完成,并不是人为的触发,我们一般可称此为定时器任务. 这类技术主要应用到那些需要进行后台整理数据的系统中,比如说 ...

  7. Android 对Map按key和value分别排序

    一.理论准备 Map是键值对的集合接口,它的实现类主要包括:HashMap,TreeMap,Hashtable以及LinkedHashMap等. TreeMap:基于红黑树(Red-Black tre ...

  8. Windows 上使用 cygwin 连接到 docker toolbox

    Windows 上使用 cygwin 连接到 docker toolbox Docker 确实给软件开发带来一些好处,在简化部署.统一开发.测试和生产环境上,有它独到的理念.Linux 上可直接安装 ...

  9. 第一段nodejs代码

    步骤一.创建服务器 接下来我们使用 http.createServer() 方法创建服务器,并使用 listen 方法绑定 8888 端口. 函数通过 request, response 参数来接收和 ...

  10. JSP常用标签——JSTL标签和EL表达式

    一.JSTL简介 1.什么是JSTL JSTL是Java中的一个定制标记库集.(这个标记库集不需要自己编写,可以直接使用) 2.为什么要使用JSTL 实现了JSP页面中的代码复用(基于标签库原理,重复 ...