埃氏篩法和尤拉篩法的區別
Eratosthenes篩法(Sieve of Eratosthenes) 由於思想非常簡單,故只給出實現。
void eratosthenes_sieve(int n) { totPrimes = 0; memset(flag, 0, sizeof(flag)); int sqrtn = sqrt(n + 0.5); for (int i = 2; i <= sqrtn; i++) { if (!flag[i]) { primes[totPrimes++] = i; for (int j = i * i; j <= n; j += i) { flag[j] = true; } } } for (int i = sqrtn + 1; i <= n; i++) { if (!flag[i]) primes[++totPrimes] = i; } }
時間複雜度 。(窩不會證明)
Euler篩法(Sieve of Euler)
尤拉篩是一種線性演算法,並且同時可以計算出每個數的 。
做法
回顧經典的Eratosthenes篩法,它可能對同一個質數篩去多次。那麼如果用某種方法使得每個合數只被篩去一次就變成是線性的了。 不妨規定每個合數只用其最小的一個質因數去篩,這便是尤拉篩了。 不妨先看程式碼:
void euler_sieve(int n) { totPrimes = 0; memset(flag, 0, sizeof(flag)); for (int i = 2; i <= n; i++) { if (!flag[i]) primes[totPrimes++] = i; for (int j = 0; i * primes[j] <= n; j++) { flag[i*primes[j]] = true; if (i % primes[j] == 0) break; } } }
請仔細體會i % primes[j] == 0的含義。 時間複雜度 。
簡單證明
這個看似很簡單,其實還是要注意一下細節的。搞清了證明其他的問題也就清楚了。 證明分兩部分。首先證每個合數都會被篩到(正確性),其次證每個合數只會被篩到一次(複雜度)。
每個合數都會被篩到
設有一合數 (為質數) 則一定會在 時被篩去(此時 ),因為對於小於 的質數,一定不會被 整除
每個合數都只會被篩到一次
與上面一樣,還是設有一合數 ( 為質數) 倘若存在一個質因子 也篩去了,那麼此時 。 ,此時在內層迴圈中已經早早地break掉了,因為 。 ,此時還沒加進質數表QwQ(順便一提:這種情況只有可能在 時發生) 難以理解的地方
i % primes[j] == 0為何不放在前面?
你可以去試試……實踐出真知。 放前面的話,所有的“某個質因子的次數不為1”的合數便會被當成質數。至於為什麼,請看證明。
j < totPrimes為何不加?
實踐才是檢驗真理的唯一標準。 當 為質數時,內層迴圈會在最後一個質數(也就是 自己)終止。當 為合數時,內層迴圈會在它的第一個質因數終止。 當然加了也沒有問題。
順便把 算出來?
其實這是極簡單的。 主要基於以下事實:(很容易通過定義推出來,不妨自己試試)
1.n為質數時,phi(n) = n - 1
2.p為質數且p整除n時,phi(np) = p phi(n)
3.p為質數且p不整除n時,phi(n*p) =(p - 1) * phi(n)
有沒有發現簡直就是為Euler篩法量身定做的! 程式碼:
void euler_sieve_with_phi(int n)
{
totPrimes = 0;
phi[1] = 1;
memset(flag, 0, sizeof(flag));
for (int i = 2; i <= n; i++) {
if (!flag[i]) {
primes[totPrimes++] = i;
phi[i] = i - 1;
}
for (int j = 0; i * primes[j] <= n; j++) {
flag[i*primes[j]] = true;
if (i % primes[j])
phi[i*primes[j]] = phi[i] * (primes[j] - 1);
else {
phi[i*primes[j]] = phi[i] * primes[j];
break;
}
}
}
}
速度比較 你可能會覺得 微不足道,那麼你錯了。 實測結果:(精確到微秒,編譯時不開啟優化開關)
when n = 10000
eratosthenes_sieve(1229): 0(us)
euler_sieve(1229): 0(us)
when n = 100000
eratosthenes_sieve(9592): 999(us)
euler_sieve(9592): 0(us)
when n = 1000000
eratosthenes_sieve(78498): 13004(us)
euler_sieve(78498): 7004(us)
when n = 10000000
eratosthenes_sieve(664579): 185130(us)
euler_sieve(664579): 79067(us)
when n = 100000000
eratosthenes_sieve(5761455): 2363692(us)
euler_sieve(5761455): 842592(us)
when n = 1000000000
eratosthenes_sieve(50847534): 25535159(us)
euler_sieve(50847534): 8987385(us)
差距還是蠻大的呢。