1. 程式人生 > 其它 >java中必須知道的常用類

java中必須知道的常用類

素數篩

素數篩,顧名思義,是一種把自然數集合[2,n]中的所有素數篩選出來的演算法,通常應用於需要素數打表的題目。

常用的素數篩演算法有兩種,分別為埃氏篩 O(nloglogn) 尤拉篩 O(n)

埃氏篩

最樸素的素數篩演算法,核心數學原理為素數的倍數為和數

思路如下:

對於一串數字:2,3,4,5,6,7,8,9,10

找到第一個未被刪除的數字2,從2開始,刪除其後所有的2的倍數:2、3、4、5、6、7、8、9、10

得到剩餘的陣列:2、3、5、7、9

接著找到下一個未被刪除的數字3,也刪除其後所有的3的倍數:23、5、7、9

得到剩餘陣列:23、5、7

接著應該找下一個未被刪除的數字5

,但是5大於sqrt(10),所以終止遍歷,不再查詢。

最終得到的素數陣列為:2、3、5、7

檢視程式碼
/*生成2-100之間的全部素數*/

#include <cstdio>

using namespace std;

bool issum[100]; 

int main() {
	for (int i = 2; i*i <= 100; i ++)
		if (!issum[i]) 
			for (int j = i*i; j <= 100; j += i)
				issum[j] = true;
	return 0;
}

程式碼詳解:

建立桶排的bool陣列,表示下標這個數是否為和數。

開始迴圈,如果數字是素數,那就刪去後續這個數的倍數;如果是和數就跳過。(和數總能寫成素數的乘積,所以全部的和數都會被刪除,不必擔心跳過和數會遺漏)

當前刪除數從i2開始,是因為對於當前素數i>2i2之前的和數總能寫成n×i×p0,其中p0為小於i的素數,這就說明i2之前的和數已通過p0刪除,無需重複刪除;而當i=2時,2、3都是素數,i2=4才為第一個要刪除的和數

共迴圈sqrt(100)=10次,這是因為任意和數的最大質因子總不大於該和數的開方值,例如100的最大質因子為5,不超過它的開方值10

尤拉篩

埃氏篩儘管提高了素數打表的效率,但它依然可能導致一個和數被重複遍歷。

例如,對於和數225

,由於225大於32,所以它會在i=3的迴圈裡被刪除一次。同時,又因為225大於52,所以它也會在i=5的迴圈裡被刪除一次。

這就降低了素數篩演算法的效率,尤拉篩則通過維護一個新的素數陣列解決了這個問題。

尤拉篩演算法簡化的核心為僅在該和數的最小質因子迴圈中把該和數刪除一次

思路如下:

對於一組數字:2、3、4、5、6、7、8、9、10、11、12

首先找到第一個數2是素數,所以放入素數陣列:{ 2 }

接著從原陣列的2開始,與素數數組裡的每一個元素依次相乘,並刪除乘積對應的數,這裡僅刪除2×2=4:2、3、4、5、6、7、8、9、10、11、12

接著找到下一個數3,放入素數陣列:{ 2、3 }

從原陣列的3開始,依次刪除與素數陣列元素的乘積:234、5、6、7、8、9、10、11、12

接著找到下一個數字4,由於4不是素數,所以不放入素數陣列,但是仍要與素數陣列{ 2、3 }相乘,並刪除乘積:234、5、6、7、89、10、11、12

這裡12不應該被4刪去,因為12的最小質因子是2,它應該在某次迴圈值與2的乘積為12時才能被刪除。

我們假想原陣列迴圈到了66不是素數,不放入素數陣列,但要與素數陣列{ 2、3、5 }相乘,並刪除乘積,12在這時才應該被刪去。

那如何確保一個數僅在它的最小質因子迴圈中被刪去呢?

在上面的所有迴圈中,迴圈到2時,素數陣列中僅有{ 2 },原陣列中的2能被素數陣列中的2整除,它們的乘積4被刪除;

迴圈到3時,素數陣列為{ 2、3 }3不能被2整除,可以被3整除,乘積6、9被刪除;

迴圈到4時,素數陣列為{ 2、3 }4能被2整除,不能被3整除,乘積8被刪除,乘積12沒有被刪除;

假想迴圈到6時,容易想到素數陣列為{ 2、3、5 }6不能被2、3、5整除,我們結合上述規律,猜測乘積12、18、30均會被刪除;

我們可以直觀地總結出當前值刪除乘積的終止條件:當原陣列中的值a能被素數陣列中的某個值p整除,就終止遍歷素數陣列

下面是數學證明:

對於找到的值a,如果滿足p0|aa≠p0,那麼對於p0之後的素數p>p0,總有a×p=n×p×p0,由於p0小於p,並且p0是第一個找到的能整除a的質因數,所以p0就是a的最小質因數,則應該在p0的迴圈中刪去a

如果p0|aa=p0,那麼p0一定為當前素數陣列的尾元素,所以遍歷也會在p0處結束。

總結一下,相比與埃氏篩,尤拉篩多維護了一個素數陣列,無論迴圈到的值是否為素數,都要與素數陣列相乘並刪去乘積,並在滿足上述終止條件時停止刪除進入下一個迴圈,如果當前迴圈到的值為素數就放入素數陣列。

檢視程式碼
/*生成2-100之間的全部素數*/

#include <cstdio>
#include <vector>

using namespace std;

bool issum[100]; 
vector<int> prime;

int main(int argc, char* argv[]) {
	prime.resize(0);
	for (int i = 2; i <= 100; i ++) {
		if (!issum[i]) prime.emplace_back(i);
		for (int j = 0; i*prime[j] <= 100 && j < prime.size(); j ++) {	// 防止乘積越界
			issum[i*prime[j]] = true;
			if(!(i%prime[j])) break;
		}
	}
	return 0;
}