Core's ink

Back

质数筛法(暴力、埃式筛、欧拉筛)Blur image

如何判断一个数是不是质数,现在求区间 [1,1e7][1,1e7] 内所有质数,学习「埃式筛法」和「欧拉筛法」之前,先介绍下暴力筛选。

可借此题验证下:204. 计数质数

1. 暴力筛选#

0 表示质数,1 表示合数。

static final int N = 1e7 + 5;
int st[N]; // 初始化为0,0表示质数,1表示合数

for(int i = 2; i <= n; i++){
	for(int j = 2; j * j <= i; j++){ //试除法
		if(i % j == 0){
			st[i] = 1; // 合数,标记为1
            break;
		}
	}
}
cpp

2. 埃式筛法#

这种方法无疑是最慢的,换一种思路:一个质数的倍数一定是合数

所以假设 P 是质数,我们可以筛选掉区间 [1,1e7][1,1e7] 中所有 P 的倍数。

为什么这样能筛去所有的合数呢,因为一个合数一定能被分解为几个质数的幂的乘积,并且这个数的质因子一定是小于它本身的,所以当我们从小到大将每个质数的倍数都筛去的话,当遍历到一个合数时,它一定已经被它的质因子给筛去了

#include <iostream>
#include <vector>

const int N = 1e7 + 5;
int st[N]; // st[i] == 1 表示 i 是合数;0 表示 i 是素数

void E_sieve(int n) {
    for (int i = 2; i <= n; i++) {
        if (st[i] == 0) {
            for (int j = 2 * i; j <= n; j += i) {
                st[j] = 1; // j 是 i 的倍数,是合数,标记为 1
            }
        }
    }
}
cpp

我们还可以对其进行优化:

  • 我们会先筛 2 的所有倍数,然后筛 3 的所有倍数,但筛除3的倍数时,我们还是从 3 的 2 倍开始筛,其实 323 * 2 ,已经被 232 * 3 时筛过了。又比如说筛 5 的倍数时,我们从 5 的 2 倍开始筛,但是 525 * 2 会先被 252 * 5 筛去,535 * 3 会先被 353 * 5 筛去,545 * 4 会先被 2102 * 10 筛去,所以我们每一次只需要从 iii * i 开始筛,因为 (23,,i1)(2,3,…,i - 1) 倍已经被筛过了。
  • 另外,判断一个数 n 是不是质数,我们只需要判断 [2,n][2,\sqrt{n}] 内有没有它的因子,在筛选合数时,我们也可以这样做,因为一个合数的最小质因子一定小于等于 n\sqrt{n}

优化后的埃式筛法:

#include <iostream>

const int N = 1e7 + 5;
int st[N]; // st[i] == 1 表示 i 是合数,0 表示 i 是素数

void E_sieve(int n) {
    for (int i = 2; i <= n / i; i++) {
        if (st[i] == 0) {
            for (int j = i * i; j <= n; j += i) {
                st[j] = 1; // j 是 i 的倍数,标记为合数
            }
        }
    }
}
cpp

时间复杂度可以近似看成 O(n)O(n)

但是我们还可以更快,那就是欧拉筛,又称为线性筛。

3. 欧拉筛法/线性筛法#

欧拉筛的核心思想就是确保每个合数只被最小质因数筛掉,或者说被合数的最大因子筛掉。

比如 120=2335120 = 2^3 * 3 * 5,120 会被 2 筛一次,3 筛一次,5 筛一次。

多做了两次不必要的操作,如何确保 120 只 2 筛选掉。

时间复杂度:O(n)O(n)

#include <iostream>

const int N = 1e7 + 5;
int st[N];           // st[i] == 1 表示 i 是合数
int primes[N];       // 存所有质数
int cnt = 0;         // 质数的个数

void ola(int n) {
    for (int i = 2; i <= n; i++) {
        if (st[i] == 0) {
            primes[cnt++] = i; // i 是质数,加入 primes 数组
        }
        for (int j = 0; j < cnt && primes[j] <= n / i; j++) {
            st[primes[j] * i] = 1; 	// 标记合数
            if (i % primes[j] == 0) // 保证每个合数只被它的最小质因子筛一次
                break;
        }
    }
}
cpp
质数筛法(暴力、埃式筛、欧拉筛)
https://coooredump.github.io/blog/leetcode/sieve-of-prime-number/
Author Coredump
Published at April 28, 2025
Comment seems to stuck. Try to refresh?✨