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

原代码:

//
#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. Java多线程 - 线程状态

    转自: http://www.cnblogs.com/lwbqqyumidi/p/3804883.html 一.线程的生命周期及五种基本状态 关于Java中线程的生命周期,首先看一下下面这张较为经典的 ...

  2. Miscellaneous--Tech

    1. Questions: 1)EF.2)MVC/MVP/MVVM.3)page lifecyle. preInit,Init,InitCompleted,preLoad,Load,LoadCompl ...

  3. dede如何实现二级栏目导航的仿制

    {dede:channelartlist row='2' typeid='1,2' }<h3><a href='{dede:field name='typeurl'/}'>{d ...

  4. 第一个Json.Net Demo

    //序列化 private void btnShow_Click(object sender, EventArgs e) { DataTable dt = new DataTable(); DataC ...

  5. MongoDB操作

    创建.删除数据库 格式 use DATABASE_NAME 如果不存在,则创建,否则直接切换到该数据库 显示当前所在的数据库 db 显示所有数据库 show dbs 删除数据库 db.dropData ...

  6. class属性中为什会添加非样式的属性值?

    来由 在一些插件中经常看到, 在class属性中出现一些跟样式无关的属性值, 这些值在css样式中没有对应定义, 但是在js中会根据这个值来给dom对象添加特殊的行为, 例如: jquery vali ...

  7. Maven开发环境的搭建,含jetty调试[简单明了]

    之前的一片老文,搬过来. 1. Maven的开发环境需要哪些部件a) Maven.去http://maven.apache.org/ 下载.将其bin目录加入进系统PATH.测试方法:用命令mvn – ...

  8. 微信成为开发者C#代码

    第一句话都会这么去写:程序猿就是苦逼,除了开发还要会写博文!今天咱就和大家探讨下如何让自己成为开发者!那么怎么才能成为开发者呢? 首先给大家一个微信的截图,看到这个截图,是不是有想去尝试的冲动?

  9. Struts2上传图片时报404错误

    可能是struts配置文件中定义的拦截器导致的,后缀拦截导致,将该拦截器去掉,在action类里判断后缀 public String upload()throws Exception{ ActionC ...

  10. Unplugging一个PDB

    Unplugging一个PDB Unplugging一个pdb不等于remove一个pdb Unplugging一个pdb会创建一个对应的xml文件,借助该xml文件可以将其添加到其他的cdb pdb ...