素數個數 <埃式篩 && 尤拉篩>

PeachyGalaxy發表於2024-11-13

求 1 ~ 1e7 以內素數的個數

最普通做法(非常超時

int n;
bool judge(int x)
{
    if(x == 1) return false;
    for(int i = 2; i < x; i++)
    {
        if(x % i == 0) return false;
    }
    else return true;
}
int main()
{
    cin >> n;
    int count = 0;
    for(int i = 2; i < n; i++)
    {
        if(judge(i)) count++;
    }
    cout << count;
}

普通做法(超時

//與上面不同的是將judge函式中的搜尋範圍減少
bool judge(int x)
{
    if(x == 1) return false;
    for(int i = 2; i <= x / i; i++)  //迴圈中的結束條件更改了
    {
        if(x % i == 0) return false;
    }
    else return true;
}
//更改方法1: i <= sqrt(x)         sqrt函式速度慢,每次迴圈都會計算一次;開根號可能有精度誤差
//更改方法2: int num = sqrt(x)    i <= num  這樣只用計算一次sqrt(x)
//更改方法3: i * i <= x           不用sqrt也沒有精度誤差,但是當i特別大時,i * i可能會溢位變為負數
//更改方法4: i <= x / i           用除法速度可能較慢,但是資料很大時建議使用

埃式篩法(快

核心
篩去得到的素數的倍數 -> 合數
實現篩去
用一個bool型別的陣列 或者 bitset(會慢幾十ms)來儲存每個值是否是素數,當前素數的倍數則是true,後續的遍歷只有滿足false的條件(即未被篩去)才能進行
不足
一個元素可能會重複篩去,因為它同時是幾個素數的倍數
例如
i = 2時,會篩掉4,6,8,10,12,14....
i = 3時,會篩掉9,12....
這裡12就篩重複了(這是已經改進了一點的結果,沒改進之前,int j = i * 2,重複篩掉的就更多,例如6)

  • 利用了3個for,但是重複篩去的資料更少,1e6的資料平均在15ms
const int N = 1e7 + 10;
bool judge[N];  //bitset<N> judge;  初始化都為false
int n;
int main()
{
    cin >> n;
    int count = 0;
    for(int i = 2; i <= n / i; i++)
    {
        if(!judge[i]) 
            for(int j = i * i; j < n; j += i) judge[j] = true;   //j = i * 2 稍微慢了一點
    }
    for(int i = 2; i <= n; i++)
    {
        if(!judge[i]) count++;
    }
    cout << count;
}
  • 利用2個for,但是重複篩去的元素更多,1e6的資料平均在15ms(時間波動有點大)
const int N = 1e7 + 10;
bool judge[N];  //bitset<N> judge;  初始化都為false
int n;
int main()
{
    cin >> n;
    int count = 0;
    for(int i = 2; i <= n; i++)
    {
        if(!judge[i]) 
        {
            count++;
            for(int j = i * 2; j <= n; j += i) judge[j] = true;   //這裡相比上面,int j 不能初始化為i * i,會溢位;並且當 i > sqrt(n) 時仍然會篩掉元素,但是這些元素肯定已經被小於sqrt(n)的數給篩過了
        }
    }
    cout << count;
}

歐式篩法(最快

核心
合數 = 最小的素數 * 另一個數,只有prime[j]是最小的質因數時i才能被篩掉
例如 24 = 3 * 8 = 3 * 2 * 2 * 2 = 2 * 12,要在另一個數為12時才能篩掉,不然就會重複
1e6資料的時間<10ms
核心程式碼

if(i % prime[j] == 0) break;

程式碼實現

#include <iostream>

using namespace std;
const int N = 1e6 + 10;
int prime[N];
bool judge[N];

int main()
{
    int n = 1e6, k = 0;
    for(int i = 2; i <= n; i++)
    {
        if(!judge[i]) prime[k++] = i;
        for(int j = 0; j < k; j++) 
        {
            if(i * prime[j] > n) break;
            judge[i * prime[j]] = true;
            if(i % prime[j] == 0) break;
        }//建議多列一串數字尋找原理,或者記下來
    }
    cout << k;
}

相關文章