日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > windows >内容正文

windows

质数筛(朴素、埃氏、欧拉)

發布時間:2023/11/23 windows 35 coder
生活随笔 收集整理的這篇文章主要介紹了 质数筛(朴素、埃氏、欧拉) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

質數篩(樸素、埃氏、歐拉)

介紹

作為和數學高度結合的一門學科,程序設計中經常會用到數學上的性質和概念,或者說,計算機一開始就是為了解決數學問題而發明的。在做題的過程中,我們經常遇到質數相關的題目,那么,我們如何判斷一個數是不是質數呢?如何把質數全部打入表中呢?今天,我將介紹三種常見的篩取質數的方法。


樸素篩

代碼實現

int main()
{
	int n, c, N = 0, prime[10000];//質數數組
	scanf("%d", &n);
	for (int i = 2; i <= n; i++)//檢測i是否為質數
	{
		c = 1;
		for (int j = 2; j * j <= i + 1; j++)//測試i是否能被j整除
			if (i % j == 0 && i != 2)
			{
				c = 0;
				break;
			}
		if(c) prime[N++] = i;//填入并計數
	}
	for (int i = 0; i < N; i++) printf("%d ", prime[i]);
	return 0;
}

分析

根據質數的定義,質數有且只有兩個因數,即1和它本身。

樸素篩就根據這最基本的性質,從2開始遍歷,直到它的平方根,依次取余,如果整除了就違反了質數有且只有兩個因數的性質,可以將其排除。

之所以只需要遍歷到平方根,是因為整除時,結果也是它的一個因數,故只需要遍歷到平方根,便可以將所有可能是因數的數試到。

for (int i = 2; i <= n; i++)//檢測i是否為質數
	{
		c = 1;
		for (int j = 2; j * j <= i; j++)//測試i是否能被j整除
			if (i % j == 0 && i != 2)
			{
				c = 0;
				break;
			}
		if(c) prime[N++] = i;//填入并計數
	}

這里是樸素篩的核心部分。

值得注意的是,for循環的跳出條件設置為j*j<=i,避免了sqrt函數的使用,可以顯著提升運行速度。

而變量c的設置則是為了標識i是否是質數,若是因為判斷為合數而跳出,則將c賦為0,后續不做處理,反之,將其存入數組。

補充

整個算法的時間復雜度為O(nlogn)。很顯然,這個算法是最基礎的暴力遍歷,如果題目給的數據大一點就會被T得很慘,比賽時間充裕的情況盡量不要用樸素篩,就跟盡量用快排別用冒泡一個道理。


埃氏篩

代碼實現

#include<stdio.h>
#include<stdbool.h>
#define maxNum 1000000001//定義最大值
bool priNum[maxNum];//質數為真,否則為假
void savePriNum()//創建預處理質數集
{
	for (int i = 0; i < maxNum; i++)
		priNum[i] = true;//默認真
	priNum[0] =priNum[1] = false;
	for (int i = 2; i * i < maxNum ; i++)//依次篩掉i的倍數,不包括i
		for (int j = 2 * i; j < maxNum; j += i)
            priNum[j] = false;
}

int main()
{
	savePriNum();
	int n;
	scanf("%d", &n);
	for (int i = 2; i <= n; i++)
		if (priNum[i])
            printf("%d\n", i);
	return 0;
}

分析

質數有且只有兩個因數,那也就是說,任何數的倍數都不可能是質數,那我們只需要在遍歷2到它的平方根,并標記這些數在要求范圍內的倍數為合數,那剩下的數就是質數了。

#include<stdbool.h>
#define maxNum 1000000001//定義最大值
bool priNum[maxNum];//質數為真,否則為假

首先,我們先創建一個布爾型數組來存放質數。因為數據范圍極大,而我們只需要存放01來標記質數合數,所以我們采用值只有truefalse的布爾型變量,來節省空間。

void savePriNum()//創建預處理質數集
{
	for (int i = 0; i < maxNum; i++)
		priNum[i] = true;//默認真
	priNum[0] =priNum[1] = false;
	for (int i = 2; i * i < maxNum ; i++)//依次篩掉i的倍數,不包括i
		for (int j = 2 * i; j < maxNum; j += i)
            priNum[j] = false;
}

我們將存表操作封裝進函數中。

首先,我們默認每個數都為質數,接著,特判01不是質數,同時,01也不在我們遍歷的過程中。

我們從2開始,遍歷到范圍最大值的平方根,標記這些數在要求范圍內的倍數為合數。

這樣,我們想判斷x是不是質數,只需要查詢priNum[x]的值就可以了。

補充

整個算法的時間復雜度為O(nloglogn),已經很逼近線性時間O(n)了,但是我們可以發現,埃氏篩在標記合數時,是有重復標記的。當一個合數擁有多個因數時,就會被標記多次,例如12擁有因數1234612,除去112,在遍歷2346時,12都被標記了一次,所以,埃氏篩還并不是線性時間。


歐拉篩

代碼實現

#include<stdio.h>
#include<stdbool.h>
#define maxNum 1000000001//定義最大值
bool priNum[maxNum];//質數為真,否則為假
int  pri[maxNum], N = 0;
void savePriNum()//創建預處理質數集
{
	for (int i = 0; i < maxNum; i++)
		priNum[i] = true;//全部填入真
	priNum[0] = priNum[1] = false;
	for (int i = 2; i * i <= maxNum; i++)
		if (priNum[i])
		{
			pri[N] = i;//存入數組并計數
			N++;
			for (int j = 0; j < N; j++)//若i為質數,則標記它和其他質數的每一個乘積
				if (pri[j] * i < maxNum) priNum[pri[j] * i] = false;
				else break;//
		}
		else
			for (int j = 0; j < N; j++)
			{
				if (pri[j] * i < maxNum) priNum[pri[j] * i] = false;//若i為合數,則標記它和其他質數的乘積
				if (i % pri[j] == 0) break;//直到i整除到某質數
			}
	return 0;
}

int main()
{
	savePriNum();
	int n;
	scanf("%d", &n);
	for (int i = 1; i <= n; i++)
		if (priNum[i])
			printf("%d ", i);
	return 0;
}

分析

歐拉篩又稱線性篩,在歐拉篩中每個合數都只會被標記一次,因此,算法的時間是線性的,時間復雜度到了O(n)

為了實現每個合數只被標記一次,在歐拉篩中我們規定每個合數都只會被它的最小因數標記,這里的意思是通過該數最小因數*某數=該數來標記該數。

#include<stdio.h>
#include<stdbool.h>
#define maxNum 1000000001//定義最大值
bool priNum[maxNum];//質數為真,否則為假
int  pri[maxNum], N = 0;

預處理時,我們另外創建一個數組,用于即時存放篩選出的質數,同時設置變量N用于記錄當前質數數量。

void savePriNum()//創建預處理質數集
{
	for (int i = 0; i < maxNum; i++)
		priNum[i] = true;//全部填入真
	priNum[0] = priNum[1] = false;
	for (int i = 2; i * i <= maxNum; i++)
		;//......
	return 0;
}

我們同樣將存表操作封裝進函數中,默認存真,特判01,同樣的遍歷至平方根,不做贅述。

if (priNum[i])
	{
		pri[N] = i;//存入數組并計數
		N++;
		for (int j = 0; j < N; j++)//若i為質數,則標記它和其他質數的每一個乘積
			if (pri[j] * i < maxNum) priNum[pri[j] * i] = false;
			else break;//
	}

當我們遍歷到一個質數時,我們將其存入質數數組并計數,然后將其與已經存入的質數相乘,并標記相乘的積為合數。

兩個不同質數相乘的積有且只有4個因數,兩個相同質數相乘的積有且只有3個因數,這是分解質因數的原理。

也因此,我們通過此法標記的數,必然是通過它的最小因數來標記的。

else
	for (int j = 0; j < N; j++)
	{
		if (pri[j] * i < maxNum) priNum[pri[j] * i] = false;//若i為合數,則標記它和其他質數的乘積
		if (i % pri[j] == 0) break;//直到i整除到某質數
	}

而當我們遍歷到一個合數時,我們同樣將其與已經存入的質數相乘,并標記相乘的積為合數。

但歐拉篩的精髓之處來了。

當該數在相乘中遍歷到自己的一個因數后,就需要break跳出,終止循環。

同樣以12舉例,當i遍歷到4j遍歷到2時,4%2==0,此時需要跳出,j不能繼續遍歷到3,若通過4*3=12來標記12,在i遍歷到6時,6*2=12便會重復遍歷,也違反了合數需要被自己的最小因子標記的規則。


總結

樸素篩和埃氏篩的實現原理是比較簡單的,使用的場景也比較廣泛,但在個別的競賽題中會T,必須使用歐拉篩。

歐拉篩理解的過程是有點難的,但在真正理解之后思路會非常清晰,主要就是合數需要被自己的最小因子標記的規則,需要細細體會。

以上便是質數篩三種篩法的介紹,本文由涼茶coltea撰寫,轉載請注明出處。

總結

以上是生活随笔為你收集整理的质数筛(朴素、埃氏、欧拉)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。