素数筛法(埃式筛、线性筛详解)

寻找素数最朴素的方法当然是一个一个遍历,我们依次遍历每一个数,然后分别判断是否是素数。所以问题的核心又回到了判断素数上,那么怎么判断一个数是不是素数呢?

素数的性质只有一个,就是只有1和它本身这两个因数,我们要判断素数也只能利用这个性质。所以可以想到,假如我们要判断n是否是素数,可以从2开始遍历到n-1,如果这n-1个数都不能整除n,那么说明n就是素数。这个我没记错在C语言的练习题当中出现过,总之非常简单,可以说是最简单的算法了。

埃式筛法

我们今天要介绍的埃拉托斯特尼算法就是他发明的用来筛选素数的方法,为了方便我们一般简称为埃式筛法或者筛法。埃式筛法的思路非常简单,就是用已经筛选出来的素数去过滤所有能够被它整除的数。这些素数就像是筛子一样去过滤自然数,最后被筛剩下的数自然就是不能被前面素数整除的数,根据素数的定义,这些剩下的数也是素数。

举个例子,比如我们要筛选出100以内的所有素数,我们知道2是最小的素数,我们先用2可以筛掉所有的偶数。然后往后遍历到3,3是被2筛剩下的第一个数,也是素数,我们再用3去筛除所有能被3整除的数。筛完之后我们继续往后遍历,第一个遇到的数是7,所以7也是素数,我们再重复以上的过程,直到遍历结束为止。结束的时候,我们就获得了100以内的所有素数。

如果还不太明白,可以看下下面这张动图,非常清楚地还原了这整个过程。

时间复杂度为O(nlognlongn)

#include<iostream>
using namespace std;
const int N = 1e7;
int prime[N + 1];
bool visit[N + 1];
//素数筛
//埃式筛
int E_sieve(int n) {
	int k = 0;
	for (int i = 0; i < n; i++) visit[i] = false;
	// 1.普通
	/* for (int i = 2; i <= n; i++) {
		if (!visit[i]) {
			prime[k++] = i;
			for (int j = i * 2; j <= n; j+=i) { // i的倍数筛选调
				visit[j] = true;
			}
		}
	}*/
	//2.优化 有些数可能会被多筛几遍 这样会影响时间复杂度
	for (int i = 2; i * i <= n; i++)
		if (!visit[i])
			for (int j = i * i; j <= n; j += i) visit[j] = true;
	int k = 0;
	for (int i = 2; i <= n; i++) { // i * i 前面的合数被更小的素数划掉了
		if (!visit[i]) prime[k++] = i;
	}
	return k; // 1~n中的素数
}


int main() {
	return 0;
}

欧拉筛法

欧拉筛法又称为线性筛法,它能在O(n)的线性时间内筛完1~n中的素数。

如合数6它可以被质数 2 和 3划掉,这时我们只要明确由最小质数划掉即可。

#include<iostream>
using namespace std;
const int N = 1e7;
int prime[N + 1];
bool visit[N + 1];
//素数筛
//埃式筛
int E_sieve(int n) {
	int k = 0;
	for (int i = 0; i < n; i++) visit[i] = false;
	// 1.普通
	/* for (int i = 2; i <= n; i++) {
		if (!visit[i]) {
			prime[k++] = i;
			for (int j = i * 2; j <= n; j+=i) { // i的倍数筛选调
				visit[j] = true;
			}
		}
	}*/
	//2.优化 有些数可能会被多筛几遍 这样会影响时间复杂度
	for (int i = 2; i * i <= n; i++)
		if (!visit[i])
			for (int j = i * i; j <= n; j += i) visit[j] = true;
	int k = 0;
	for (int i = 2; i <= n; i++) { // i * i 前面的合数被更小的素数划掉了
		if (!visit[i]) prime[k++] = i;
	}
	return k; // 1~n中的素数
}
bool vis[N + 1];
int euler_sieve(int n) {
	int cnt = 0;
	memset(vis, 0, sizeof(vis));
	memset(prime, 0, sizeof(prime));

	for (int i = 2; i <= n; i++) {
		if (!vis[i]) prime[cnt++] = i;
		for (int j = 0; j < cnt; j++) {
			if (i * prime[j] > n) break; //越界的不需要了
			vis[i * prime[j]] = 1;//prime中全部的素数相乘一定式合数
			if (i % prime[j] == 0) break; //  每个合数只被最小质因子划掉 
		}
	}
	return cnt;
}
int main() {
	return 0;
}

猜你喜欢

转载自blog.csdn.net/zhi6fui/article/details/128633372