如何用 C/C++ 求 1 到 1000 內的所有完全數?
完全數,即該數所有不包括其本身的因子和為該數本身。
這是個小題目,按定義窮舉算是基本的練習,並太多特別值得說的地方。選個合適的語言,也就是一兩行代碼按定義表示出來,比如 Python:
[n for n in range(1, 1000)
if sum(k for k in range(1, n) if n%k == 0) == n]
另一方面這又是個數學題目。可以想見會有人想出一些辦法去做「優化」,卻輕視編程碼代碼之前的數學與演算法上的分析。實際上,如果未經深思熟慮,這類優化能起到的效果甚微,在本質上不能減少多少運算。因此,倒不如仔細從數學角度好好看看這個問題,給出一個完全不一樣的演算法,或許會讓這個原本不起眼的小題目吃起來更有嚼頭一些。
---
由 Euclid-Euler 定理,一個數 n 是偶完全數,當且僅當,其中 是一個梅森素數。
奇完全數是否存在,是當今數論中未解決的問題。但已有文獻指出,奇完全數即使存在,則不小於 ,因此在合理範圍內無須考慮。
於是現在問題歸結到求一組較小的 k 使 是梅森素數,然後套用 Euclid-Euler 定理公式計算。
驗證梅森素數的方法,較為有效的是 Lucas–Lehmer 檢驗法。即定義
從而 是素數當且僅當其中 k 是奇數,由梅森素數的性質,等價於 k 是奇素數。k 為偶數 2 則是特殊情形, 是素數。為方便計算,事實上並不需要求出所有的 ,只要求得餘數即可(否則中間結果會很大)。而這個條件用計算機非常容易驗證。
由於只要求 1000 以內的完全數,因此用 n 的表達式粗略地估計,最多只要求出 1000 以內的梅森素數。注意到 2 的 10 次方即超出 1000,因此只需要考慮 k 小於 10 的情形進行驗證。因此這也是很有效的演算法,遠好於用定義試除計算。
下面是代碼:#include &
inline long mersenne(int k)
{
return (1L &<&< k) - 1L;
}
// euclid_euler(k) is perfect if mersenne(k) is prime
// http://en.wikipedia.org/wiki/Euclid%E2%80%93Euler_theorem
inline long euclid_euler(int k)
{
return ((1L&<&
根據 @劉海洋的答案里提到的 Euclid-Euler 定理寫的:具體定理可看他的答案運算將在編譯時完成(真要說也算是優化吧...):
#include &
#define MAXN_NUM 1000
template &
struct WHILE;
template &
struct WHILE{
template & class F&>
struct BODY{
inline static void EXECUTE(){
struct DO_NOTING{
inline static void EXECUTE(){}
};
IF&
IF&
}
};
};
template &
struct IF;
template &
struct IF &< true, THEN, ELSE &> {
typedef THEN VALUETYPE;
};
template &
struct IF &< false, THEN, ELSE &> {
typedef ELSE VALUETYPE;
};
template &
struct INTEGER{
static const int VALUE = N;
};
template &
struct BOOL{
static const bool VALUE = N;
};
template &
struct MERSENNE{
static const int VALUE = (1 &<&< N)-1;
};
template &
struct EUCLID_EULER{
static const int VALUE = ((1 &<&< N) - 1)&<&<(N-1);
};
template &
struct EUCLID_EULER_LESS_THAN{
template &
struct BODY{
static const bool VALUE = EUCLID_EULER&
struct LUCAS_LEHMER_TEST;
template &<&>
struct LUCAS_LEHMER_TEST &< 2 &> {
static const bool VALUE = true;
};
template &
struct LUCAS_LEHMER_TEST{
template &
struct _LUCAS_LEHMER_TEST;
template &<&>
struct _LUCAS_LEHMER_TEST&<0&>{
static const int VALUE = 4;
};
template &
struct _LUCAS_LEHMER_TEST{
static const int VALUE = (_LUCAS_LEHMER_TEST&
};
static const bool VALUE = IF&
};
template & class F&>
struct PRINT{
template &
struct FUNC {
inline static void EXECUTE() {
std::cout &<&< F&
struct PERFECT_NUMBER{
template &
struct FUNC {
inline static void EXECUTE() {
struct DO_NOTING{
inline static void EXECUTE(){}
};
IF&
}
};
};
int main()
{
WHILE&<2, EUCLID_EULER_LESS_THAN&
return 0;
}
// Result:
//6 28 496
偽代碼
input(n)
for i &<- 1 to n do
for j &<- 1 to i-1 do
if i mod j = 0 then
sum &<- sum + j
if sum &> i then
break
if sum = i then output(i)
sum &<- 0
樓上有個複雜度為N√N的,這裡試著給一個NlogN的思路……
這是大一新生的一個作業題,有誤之處請多包涵_(:з」∠)_
曾經做過一道題,利用打表來獲得一定範圍內的質數表(即埃拉特斯尼篩),這種方法速度較快。從這裡受到啟發,試著改進一下計算方法。思路如下(設定尋找完美數的上限為n):
- 設某一個大於2但是小於n/2的整數為i;
- 記大於i,小於n的某個i的倍數 ki (如2i,3i……)為j,則j中的因子一定含有i;
- 將i從「2 → n/2」遍歷, j 從 「2i → 小於n的最大ki」遍歷, 這樣便可獲取每個數對應的所有包含自己為因子的數,也即獲得每個數的因子集合。
- 將x與vector[x]下的所有元素之和比較,來判定x是不是完美數。可以先判定vector[x]的size是否為1(是否為質數)來減少操作次數。
步驟3 C++實現如下:
// 每個下標對應的vector代表該下標對應整數的所有因子(除了自己)
vector&
for (int i = 2; i &<= n/2; i++)
for (int j = i + i; j &<= n; j+=i)
factors[j].push_back(i);
步驟4 C++實現如下:
for (int i = 0; i &< factors.size(); i++)
{
if (factors[i].size() == 1) // 因子只有1一個
continue;
else
{
int sum = 0;
for (int j = 0; j &< factors[i].size(); j++)
sum += j;
if (i == sum)
{
// i是完全數,做對應的事情
}
}
}
這種做法的時間複雜度:
步驟3:
- 初始化複雜度n+1;
- 構造(0, n]所有數的因子集合時,對於每一個i,得到的j有n/i個,每個j執行一次push_back(),時間複雜度為
步驟4:
- 對於每個整數i,其因子的個數不易得到,但我們可以考慮1~n的所有因子數目(可重複),其正是步驟3中第二層循環執行的次數(也即j被push_back()的次數),故同理,其複雜度也為2nln(n/2)。
- 判斷次數為「n + 合數數量」,姑且算為2n。
這樣,總時間複雜度為4nln(n/2)+4n,即為O(nlogn)級,比正常遍歷過程的n^2級要好。
我節選我的ACM書上一段話:
當然,完全數是很少的,迄今為止,也不過發現29個。
所以我們完全可以這麼寫程序:
main(){puts("6,28,496");}
PS:第8個完全數已無法用long long 保存,完全數的稀疏程度可見一斑。這。。。學倆禮拜c語言差不多就會了吧,一個函數用來判斷什麼是完全數,這個函數裡面應該包含一個for循環。。。當然你要是追求最優解當我沒說
#include &
if(a%i==0) s+=i;
} if(s==a){ for(i=1;i&<=a/2;i++) if(a%i==0){ if(i==1) cout&<& else cout&<&<"+"&<& } cout&<&return 0;
}不會有人的代碼比我更傻吧
如果拋開數學層面的思考,只考慮計算機層面的東西,那最快的辦法就是查表。
感覺1L太狠了,講了那麼一堆數學……這就是剛學C語言的一個小朋友的作業題吧,根本用不到數據結構與演算法,只是用來熟悉循環怎麼寫的而已。
好吧我今天心情好,就胡亂寫一點東西:
核心就是枚舉1~1000中的每一個數k,然後判斷因子之和是否等於它本身。前一步的枚舉量是的,後一步需要枚舉判斷1到k之間的數是不是k的因數,共次除法運算,因此總的時間複雜度是。不過有一個更快的枚舉k的因數的方法是,先依次枚舉,再依次枚舉。
這樣枚舉因數只需要的時間,總的時間複雜度是。
害怕污染粉絲時間線,匿了。for (int i=2; i&<=1000; i+=2)
{
int sum = 0;
for (int j=1; j&<=i/2; ++j)
if (i%j == 0) sum += j;
if (sum == i) std::cout &<&< i &<&< " ";
}
推薦閱讀:
※reinterpret_cast把const string*轉換成const char*出錯?
※為什麼用了using namespace std會報錯?
※c++如何在編譯期判斷一個對象是否是字元串字面值?
※C++的std::thread是怎麼進行參數傳遞的?