求完數問題
【題目2-10】
一個數如果恰好等於它的因子之和,這個數就稱為"完數"。例如,6的因子為1、2、3,而6=1+2+3,因此6是“完數”。編程序找出1000之內的所有完數,並按下面格式輸出其因子:
6 Its factors are 1 2 3
——譚浩強 ,《C程序設計(第四版)學習輔導》,清華大學出版社,2010年7月,p43
【評析2-10-1】
6的因子(divisor)有1、2、3和6共4個,所以所謂的完數(perfect number)顯然絕非指那些“一個數如果恰好等於它的因子之和”的數。
在數論中,完數(perfect number)指的是一個正整數,它等於其正的真因數(proper positive divisors)之和。所謂正的真因數不包括該正整數自身。
【樣本2-10】
1. #define M 1000 //定義尋找范圍 2. #include <stdio.h> 3. int main() 4. { 5. int k1,k2,k3,k4,k5,k6,k7,k8,k9,k10; 6. int i,a,n,s; 7. for(a=2;a<=M;a++) //a是2~1000之間的整數,檢查它是否完數 8. {n=0; //n用來累計a的因子的個數 9. s=a; //s用來存放尚未求出的因子之和,開始時等於a 10. for(i=1;i<a;i++) //檢查i是否a的因子 11. if(a%i==0) //如果i是a的因子 12. {n++; //n加1,表示新找到一個因子 13. s=s-i; //s減去已找到的因子,s的新值是尚未求出的因子之和 14. switch(n) //將找到的因子賦給k1~k9,或k10 15. {case 1: 16. k1=i;break; //找到的第1個因子賦給k1 17. case 2: 18. k2=i;break; //找到的第2個因子賦給k2 19. case 3: 20. k3=i;break; //找到的第3個因子賦給k3 21. case 4: 22. k4=i;break; //找到的第4個因子賦給k4 23. case 5: 24. k5=i;break; //找到的第5個因子賦給k5 25. case 6: 26. k6=i;break; //找到的第6個因子賦給k6 27. case 7: 28. k7=i;break; //找到的第7個因子賦給k7 29. case 8: 30. k8=i;break; //找到的第8個因子賦給k8 31. case 9: 32. k9=i;break; //找到的第9個因子賦給k9 33. case 10: 34. k10=i;break; //找到的第10個因子賦給k10 35. } 36. } 37. if(s==0) 38. { 39. printf("%d,Its factors are ",a); 40. if(n>1)printf("%d,%d",k1,k2); //n>1表示a至少有2個因子 41. if(n>2)printf(",%d",k3); //n>2表示a至少有3個因子 42. if(n>3)printf(",%d",k4); //n>3表示a至少有4個因子 43. if(n>4)printf(",%d",k5); //以下類似 44. if(n>5)printf(",%d",k6); 45. if(n>6)printf(",%d",k7); 46. if(n>7)printf(",%d",k8); 47. if(n>8)printf(",%d",k9); 48. if(n>9)printf(",%d",k10); 49. printf("\n"); 50. } 51. } 52. return 0; 53. }
【評析2-10-2】
這代碼!丑的簡直驚天地泣鬼神。
5. int k1,k2,k3,k4,k5,k6,k7,k8,k9,k10;
一上來就一口氣定義了10個變量,頗有愚公移山的氣概,吃奶的力氣都使出來了。
可惜的是從名字上根本看不出這些變量是做什么用的。實際上這是一種惡劣的命名方法。從后面的代碼來猜,這些變量應該是用於存放各個因子的。但是為什么要定義十個呢?毫無根據。
是1000之內的自然數的因子不多於十個嗎?這是壓根不成立的。譬如,210有1,2,3,5,6,7,10,14,15,21,30,35,42,70,105,210共16個因子,即使不考慮其自身,也有15個因子。定義10個變量存放因子顯然是一種粗暴且武斷的做法,因此代碼無疑是錯誤的。如果它能輸出正確的結果,那也只不過是瞎貓碰到死耗子的巧合而已。
6. int i,a,n,s;
這里最多需要定義一個變量a用於完成循環。一起定義了4個說明思維漂移太遠,不清楚當下到底應該做什么。有句話叫活在當下,編寫代碼也是如此,不用急着定義當下根本用不着的變量。
7. for(a=2;a<=M;a++) //a是2~1000之間的整數,檢查它是否完數 8. {n=0; //n用來累計a的因子的個數 9. s=a; //s用來存放尚未求出的因子之和,開始時等於a 10. for(i=1;i<a;i++) //檢查i是否a的因子 11. if(a%i==0) //如果i是a的因子 12. {n++; //n加1,表示新找到一個因子 13. s=s-i; //s減去已找到的因子,s的新值是尚未求出的因子之和 14. switch(n) //將找到的因子賦給k1~k9,或k10 15. {case 1: 16. k1=i;break; //找到的第1個因子賦給k1 17. case 2: 18. k2=i;break; //找到的第2個因子賦給k2 19. case 3: 20. k3=i;break; //找到的第3個因子賦給k3 21. case 4: 22. k4=i;break; //找到的第4個因子賦給k4 23. case 5: 24. k5=i;break; //找到的第5個因子賦給k5 25. case 6: 26. k6=i;break; //找到的第6個因子賦給k6 27. case 7: 28. k7=i;break; //找到的第7個因子賦給k7 29. case 8: 30. k8=i;break; //找到的第8個因子賦給k8 31. case 9: 32. k9=i;break; //找到的第9個因子賦給k9 33. case 10: 34. k10=i;break; //找到的第10個因子賦給k10 35. } 36. } 37. if(s==0) 38. { 39. printf("%d,Its factors are ",a); 40. if(n>1)printf("%d,%d",k1,k2); //n>1表示a至少有2個因子 41. if(n>2)printf(",%d",k3); //n>2表示a至少有3個因子 42. if(n>3)printf(",%d",k4); //n>3表示a至少有4個因子 43. if(n>4)printf(",%d",k5); //以下類似 44. if(n>5)printf(",%d",k6); 45. if(n>6)printf(",%d",k7); 46. if(n>7)printf(",%d",k8); 47. if(n>8)printf(",%d",k9); 48. if(n>9)printf(",%d",k10); 49. printf("\n"); 50. } 51. }
這個實在令人無語。這恐怕是C語言有史以來最長的for語句,整整寫了45行,絕對應該載入史冊。盡管前輩編程達人曾經諄諄告誡過我們,Don’t be too clever(不要過於機靈),但是依本人粗陋的理解,這絕對不是讓我們過於愚蠢。
9. s=a; //s用來存放尚未求出的因子之和,開始時等於a
從后面的代碼來看,s根本不是如注釋中所說的那樣“用來存放尚未求出的因子之和”,因為誰也不知道,尚未求出的因子和是多少,但是s的初值卻是a。所以,注釋和代碼中至少有一個是口是心非地在撒謊。
14.~35.行是錯誤的。原因前面提到過,1000以內的正整數的真因子可能多於10個。整整12行啊!悲催無比。程序員最大的悲劇是什么?就是不辭辛苦兢兢業業認認真真吭哧癟肚地寫出了一大堆繁復無比比爛棉花套還要復雜的代碼之后,暮然回首,卻被指出那代碼完全是錯誤的。
至此,我們同樣不難判斷出37.~49.行同樣是錯誤的。這很自然。程序的全部基礎都建立在不靠譜的
5. int k1,k2,k3,k4,k5,k6,k7,k8,k9,k10;
基礎之上。這個基礎既然是荒謬的,整個代碼必然是錯誤的,想改都沒法改。只能將之完全推倒重來。
最后還有一個錯誤不容易被發現。這個程序的輸出是:
6,Its factors are 1,2,3
28,Its factors are 1,2,4,7,14
496,Its factors are 1,2,4,8,16,31,62,124,248
這個輸出與題目要求的格式:
6 Its factors are 1 2 3
並不一致。
【重構2-10】
1 #include <stdio.h> 2 3 #define TOP 1000 4 5 int main( void ) 6 { 7 int n ; 8 for( n = 1 ; n <= TOP ; n++) 9 { 10 int fac , sum ; 11 for( sum = 0 , fac = 1 ; fac < n ; fac++) //求真因子和 12 if( n % fac == 0 ) 13 sum += fac ; 14 15 if( sum == n ) //真因子和與整數相等 16 { 17 printf("%d是一個完全數,其真因子為:",n); 18 for( fac = 1 ; fac < n ; fac++) 19 if( n % fac == 0 ) 20 printf(" %d",fac); 21 22 putchar('\n'); 23 } 24 } 25 26 return 0; 27 }