原文:“練習:求完數問題”
原代碼:
// #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之“練習:求完數問題”(下)