幫初學者改代碼——playerc之「練習:求完數問題」(上)

原文:「練習:求完數問題」 html

原代碼: 算法

// 
#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()函數。把代碼改爲下面的樣子會更清晰:post

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爲因子數組的尺寸。url

  從邏輯上講,這兩條預處理命令的次序應該顛倒一下,由於因子數組的尺寸是根據TOP肯定的。spa

  1024這個尺寸太大了,實際上用不到這麼大的數組。做者是發現數組尺寸定爲12不夠,20又不夠,最後索性定爲1024的。雖然定了這麼大的尺寸,但是仍是不放心,在代碼中又寫了數組尺寸不夠時的處理代碼。這些代碼實際上是沒必要要的。與其花功夫寫這些沒必要要的代碼,其實不如花工夫事先認真地估算一下所須要數組的大小。code

  不大於10000的正整數,因子不超過64個,這是個人估計。理由以下:htm

  一個數因子數的個數,取決於這個數素因子的個數,素因子越多,因子也越多。例如:

  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之「練習:求完數問題」(下)

相關文章
相關標籤/搜索