詳解埃氏篩選法篩選質數(C++實現)

說明:篇中的nN都是同一個意義,大小寫不過是爲了表現具體和一般形式而已,穿插着用可能讓讀者容易混淆,請多體諒。

一、質數定義

指在大於1的整數中,只能被1和它本身整除的數。

二、埃氏篩選法最重要的結論:

N有因數的話,那麼至少有一半的因數不會超過N\sqrt{N}
舉個例子,要判斷100是不是質數,100 = 10 X 10,只要有1個因數 > 100\sqrt{100},必然另1個因數 < 100\sqrt{100},這樣只要判斷10以內有無100的因數即可,使用這種方法的時間複雜度爲O(n*√n)。
可能這樣你還不是很懂,繼續往下看。

三、找到[0,n]範圍內所有素數 的算法基本思路

  1. 首先將0、1排除:

  2. 創建從2到n的連續整數列表,[2,3,4,…,n];

  3. 初始化 p = 2,因爲2是最小的質數;

  4. 枚舉所有p的倍數(2p,3p,4p,…),標記爲非質數(合數);

  5. 找到下一個 沒有標記 且 大於p 的數。如果沒有,結束運算;如果有,將該值賦予p,重複步驟4;

  6. 運算結束後,剩下所有未標記的數都是找到的質數。

可以結合下面這張動圖理解:
在這裏插入圖片描述

四、應用埃氏篩選法優化後的思路

我們發現 [0, N] 範圍內很多 > N\sqrt{N}的數其實是[0, N\sqrt{N}]範圍內數的倍數。而 > N\sqrt{N} 且 非[0, N\sqrt{N}]範圍內數字的倍數的,都是質數。
舉個例子: [0, 100] 範圍內很多 > 100\sqrt{100}的數其實是[0, 100\sqrt{100}]範圍內數的倍數(12,14,16是2的倍數,12,15,18是3的倍數…)。而 > 100\sqrt{100} 且 非[0, 100\sqrt{100}]範圍內數字的倍數的(11,13,17…),都是質數。

所以對 標題三 的基本算法思路做出如下優化:
對於步驟4,可以不用從2p開始排除,而是直接從 p2p^2 開始。理由已經在開始講過,所有的小於 p2p^2 的合數都有更小的因數而被排除。

對於步驟5,當 p2p^2 > n 的時候停止計算。


參考鏈接:
《使用埃拉托色尼篩選法在21億內快速查找質數》
《埃拉托色尼篩選法巧解質數問題》


當範圍在int範圍內:

#include<iostream>
#include<cstdio>
using namespace std;
    
const int maxn=5000000;
long prime[maxn];    // 存儲一個個確定爲質數的數
bool is_prime[maxn+1];    // 標記範圍內所有數
int p = 0;
int sieve(int n)
{
 	p = 0;
	for(int i=0;i<=n;i++)
		is_prime[i]=true;       // 所有數先標記爲true
    is_prime[0] = is_prime[1] = false;   // 把數字0,1標記爲質數
    for(int i=2;i<=n;i++)
    {
       	if(is_prime[i])         // 如果這個數沒有被標記爲false
    	{
             prime[p++]=i;       // 用prime數組存起來這個數,既存起了質數,又用p表示了質數個數
             for(int j=i*i;j<=n;j+=i)   // 這裏沒有優化時的寫法是for(int j=2*i; j<=n; j++)。
	    	//因爲小於j(即i^2)內的合數都因爲(根號j)(即i)內有更小的j的的因數而被排除
    							// 比如3^2 = 9,爲什麼不算2*3 = 6呢, 因爲6<9,所以6因爲3以內有更小的因數而直接被排除
    				is_prime[j]=false;
        }
    }
    return p;          // 返回質數個數
}
int main()
{
	int n;
	while(~scanf("%d",&n))
    {
    	printf("質數個數是: %d\n",sieve(n));
    	printf("質數有:\n");
    	for (int i = 0; i<p; i++)
    	{
    		printf("%d ", prime[i]);
    		printf("\n\n");
        }
   	}
    system("pause");
}	

當範圍超過了int

static const int N = 1e7;
bool is_prime[N];   // 判斷是否是素數
ll prime[N];       // 存儲素數
ll sieve(ll num)
{
	int inx = 0;
	for (int i = 0; i<=N; i++)
		is_prime[i] = true;

	is_prime[0] = is_prime[1] = false;

	int MIN = (num > N) ? N : num;

	for (ll i = 2; i<=MIN; i++)
	{
		if (is_prime[i])
		{
			prime[inx++] = i;

			for (ll j = i*i; j<=num; j+=i)
				is_prime[j] = false;
		}
	}
	return inx;
}

int gcd(int inx)    // 此處由於傳進來都是質數,所以直接相乘即爲gcd
{
	int res = 1;
	for (int i = 0; i<inx-1; i++)
		res *= prime[i];
	return res;
}

void C3()
{
	ll num;     // 輸入數
	int p;       // 最小公約數

	cin >> num;

	int inx = sieve(num);   // 篩選素數

	cout << gcd(inx) << endl;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章