素数筛选

素数

​ 素数又称之为质数,若一个数只能被1和本身整除,没有其余的约数,这我们称这个数是素数,反之,这个数是合数;而且1既不是素数也不是合数。

素数的判定

朴素算法

bool isprime(int n){//单个数判断
	for(int i = 2; i < n; i++){
        if(n % i == 0) return false;
    }
	return true;
}

但是这种方法的时间复杂度很大,如果让你判断一个很大的数是否是素数,这种方法就会爆。那我们要怎样优化。

优化

首先,我们需要知道一个定理:任何一个正整数都可以用两个因数表示,例如\(12=2\times6\)
现在我们可以来证明一个定理:一个正整数n最多只有一个大于sqrt(n)的因数
因为\(n=a\times b\)\(n = \sqrt{n} \times \sqrt{n}\); 所以\(a \times b = \sqrt{n} \times \sqrt{n}\); 现在如果\(a\geq \sqrt{n}\)并且\(b\geq \sqrt{n}\),则\(a\times b\)一定大于\(n\);同理如果\(a \leq \sqrt{n}\)并且\(b \leq \sqrt{n}\),则\(a \times b\)一定小于\(n\),所以\(n = a \times b\)中,至少有一个数是大于\(\sqrt{n}\),一个小于\(\sqrt{n}\)。 所以在在枚举时,我们就不用再枚举到\(n-1\),枚举到\(\sqrt{n}\)就可以了

bool isprime(int n){
    for(int i = 2; i <= sqrt(n); i++){
        if(n % i == 0) return false;
    }
    return true;
}

6倍数判别法(快速判断)

原理:大于等于5的素数与6的倍数相邻。
证明:所有的自然数都可以使用集合\(A=\{6n,6n+1,6n+2,6n+3,6n+4,6n+5\}\)表示,\(n\geq 0\),显然,子集\(B = \{6n, 6n+2, 6n+3, 6n+4\}\)内的元素都不会是素数,所以只有\(6n+1\)\(6n+5\)可能是素数,所以素数肯定可以使用\(6n+1\)\(6n+5\)中的一个形式表示,即大于等于5的素数与6的倍数相邻。
判断:在判断一个数k是否是素数的时候,我们只需要判断这个数是否可以使用\(6n+1\)\(6n+5\)表示,并且要判断\(t=6n+1\)\(t=6n+5\)是否是k的因子即可。

#include <bits/stdc++.h>
using namespace std;

bool Isprime(int a){
	if(a <= 1) return false;//如果这个数小于等于1,则这个数一定不是素数
	if(a == 2 || a == 3) return true;
	else if(a % 6 != 1 && a % 6 != 5) return false;//如果这个数不能使用6n+1和6n+5表示,则这个数不是素数
	for(int i = 5; i <= sqrt(a); i+=6){
        //若这个数可以使用6n+1和6n+5表示,但是它是前面素数的倍数,则这个数不是素数,例如55 = 6 * 9 + 1 = 11 * 5
		if(a % i == 0 || a % (i+2) == 0) return false;
	} 
	return true;
}

int main(){
	for(int i = 1; i <= 10000; i++){
		if(Isprime(i)) cout << i << " is prime" << endl;
	}
	return 0;
}

埃氏筛法

这个筛选方法是求自然数\(n\)以内的全部素数。
我们都知道任何一个数都可以被多个素数表示出来,于是我们在求素数时,可以把这个素数的倍数全部剔除。
例如:给出要筛选数值的范围\(n\),找出以内的素数。我们先用2去筛选,即留下2,把2的倍数全部剔除,然后再用下一个素数(也就是3),把3留下,把3的倍数全部剔除…………

const int MAXN = 1e6+10;//要筛选的范围
bool prime[MAXN];//用于记录这个数是不是素数
int arr[MAXN], t = 1;//arr存放素数
void Sieve_Prime(){
	memset(prime,true,sizeof(prime));//把所有的数都先表示位素数
	prime[0] = prime[1] = false;
	for(int i = 2; i <= MAXN; i++){
		if(prime[i]){//如果这个数是素数
			for(int j = i + i; j <= MAXN; j += i){//这个数的倍数全部表示位非素数
				prime[j] = false;
			}
			arr[t++] = i;//用另一个数组保留素数
		}
	}
}

欧拉筛选

在埃氏筛选中,存在有些数被重复多次筛选,例如:\(30\)这个数,\(30=2 \times 3 \times 5\),会被2的倍数筛选一次,3的倍数筛选一次,5的倍数筛选一次,这造成了筛选效率不是最优的。欧拉筛选法基于这种筛选进行了优化,在线性筛选中时间复杂度时O(N)。
欧拉筛选的核心思想:对于每一个合数,我们用其最小的质因子筛选它。
例如:(我们假定arr[]数组中存放这已经确定的素数)合数\(n = a\)(最小质因子)\(\times b\);如果\(n\%arr[j] ==0\);则\(n \times arr[j+1] = a\times b\times arr[j+1]\)可以被其后面的\(b\times arr[j+1]\)再乘以素数\(a\)筛选出来,所以当我们遇见\(n\%arr[j] ==0\)时要停止。

const int MAXN = 1e6+10;
bool prime[MAXN];
int arr[MAXN], t = 1;//存储素数
void Sieve_Prime(){
	memset(prime,true,sizeof(prime));
	prime[0] = prime[1] = false;
	for(int i = 2; i <= MAXN; i++){
		if(prime[i]) arr[t++] = i;//如果i是素数
		for(int j = 1; j <= t && i * arr[j] <= MAXN; j++){
			prime[i*arr[j]] = false;
			if(i%arr[j] == 0) break;//停止筛选
		}
	}
}
posted @ 2021-03-15 21:39  h星宇  阅读(121)  评论(0编辑  收藏  举报