埃拉託斯特尼篩法
從2開始,將每個素數的各個倍數(一倍除外),標記成合數。一個素數的各個倍數,是一個差爲此素數本身的等差數列。此爲這個篩法和試除法不同的關鍵之處,後者是以素數來測試每個待測數能否被整除。埃拉託斯特尼篩法(英語:sieve of Eratosthenes ),簡稱埃氏篩,也稱素數篩。這是一種簡單且歷史悠久的篩法,用來找出一定範圍內所有的素數。埃拉託斯特尼篩法是列出所有小素數最有效的方法之一。
算數基本定理
每個整數 n >= 2 可唯一分解成素數乘積 n = p1p2…pr.
普通線性篩法
由算數基本定理可知,任何一個大於等於 2 的數,都可以表示成兩個數相乘的形式,只有 1 和 它本身這一對組合的是素數,剩下的都是合數。對於一個合數 a,則一定等於某個素數 b 與另一個素數或者合數 c 相乘。也就是換句話說,通過篩掉所有素數的倍數,就可以將所有的合數篩掉而只剩下素數,於是有了如下的普通線性篩法。#include <stdio.h>
#include <string.h>
#define MAX (int)1e5 //待判斷素數範圍0~MAX
#define ll long long
ll prime[MAX + 1], cnt;
bool isPrime[MAX + 1];
void init_prime()
{
cnt = 1;
memset(isPrime, 1, sizeof(isPrime));
isPrime[0] = isPrime[1] = 0;
for (ll i = 2; i <= MAX; i++) {
if (!isPrime[i]) continue;//跳過合數
prime[cnt++] = i;//保存素數
for (ll j = 2 * i; j <= MAX; j += i)
isPrime[j] = 0;//素數倍數都不是素數
}
}
int main()
{
init_prime();
for (ll i = 1; i < cnt; i++)
printf("%lld%s", prime[i], i == cnt - 1 ? "\n" : " ");
return 0;
}
快速篩法
再次思考普通線性篩法,一個合數 n = p1p2…pr.(pi都是素數,1 <= i <= r>),是p1的倍數,也是p2的倍數,……也是pr的倍數,換句話說,該合數被重複篩了 r 次。顯然,最好的結果是每個合數只被篩一次,算法是線性的時間。那麼該如何去做呢?一種解決辦法是約定每次只在 pmin 處,對該合數 n 做篩除。
#include <stdio.h>
#include <string.h>
#define MAX (int)1e2
#define ll long long
ll prime[MAX + 1], cnt;
bool isPrime[MAX + 1];
void init_prime()
{
cnt = 1;
memset(isPrime, 1, sizeof(isPrime));
isPrime[0] = isPrime[1] = 0;
for (ll i = 2; i <= MAX; i++) {
if (isPrime[i])
prime[cnt++] = i; //位置1
for (ll j = 1; j < cnt && i * prime[j] <= MAX; j++) {
isPrime[i * prime[j]] = 0; //位置2
if (!(i % prime[j])) break; //位置3
}
}
}
int main()
{
init_prime();
for (ll i = 1; i < cnt; i++)
printf("%lld%s", prime[i], i == cnt - 1 ? "\n" : " ");
return 0;
}
這個算法可以保證每個數字都只被標記一遍,實現方法如下:
對於所有大於1的數,分爲素數和合數,而合數又分爲:
- 一個素數乘以一個素數
- 一個素數乘以一個合數
在位置 1,得到了所有素數,在位置 2 標記了所以合數。
在位置 2 的時候,又分爲兩種可能:
- i 是素數,prime[j] 是素數
- i 是合數,prime[j] 是素數
如上解釋了爲何可以標記合數
那麼是如何不重複標記合數的呢?
對於 n = pmin * c, 更具最小約定應該有 c >= pmin,反過來即是每次拎一個數 c’,只需要到小於等於它的素數範圍做乘法篩除即可。可是我們發現,依然會出現 34 = 26 這樣的重複篩除,該如何避免這樣的重複呢?依靠位置 3。
-
對於 i 是素數的情況,這個和下一個素數 prime[j + 1] 繼續標記肯定不會重複;
-
對於 i 是合數的情況,如果這個合數分解質因數後:
-
最小的質因數等於 prime[j], 則若繼續與下一個素數 prime[j + 1]相乘標記合數,就違反了不重複的約定-----因爲對於由 i * prime[j + 1] 標記的合數,顯然有更小的素數 prime[j] 去做乘積來標記。所以這種情況就應該停止迭代。
-
最小質因數大於 prime[j],則就可以繼續與下一個素數相乘繼續標記,下一輪迭代不會出現重複標記的情況。
-