埃氏筛:高效求解素数的经典算法
在算法题中,我们经常会遇到这样的问题:
给定一个正整数
n,求出1 ~ n中所有的素数。
最直接的做法是:对每个数 x,判断它是否只能被 1 和自身整除。但如果对每个数都从 2 试除到 sqrt(x),整体复杂度会比较高。
这时,经典的 埃氏筛 就派上用场了。
什么是埃氏筛?
埃氏筛,全称 埃拉托色尼筛法,是一种用于快速筛选素数的算法。
它的核心思想很简单:
如果一个数是素数,那么它的倍数一定不是素数。
例如:
2是素数,那么4, 6, 8, 10...都不是素数3是素数,那么6, 9, 12, 15...都不是素数5是素数,那么10, 15, 20, 25...都不是素数
通过不断标记素数的倍数,我们就能筛掉所有合数,剩下的就是素数。
算法过程
假设我们要求 1 ~ n 中的所有素数。
- 创建一个布尔数组
is_prime,初始认为所有数都是素数。 - 将
0和1标记为非素数。 - 从
2开始遍历到sqrt(n)。 - 如果当前数
i仍然是素数,就把i的所有倍数标记为非素数。 - 最后数组中仍然为
True的下标就是素数。
为什么从 i * i 开始标记?
很多实现中会这样写:
for j in range(i * i, n + 1, i):而不是从 2 * i 开始。
原因是:
对于素数 i,小于 i * i 的倍数已经被更小的素数筛过了。
例如,当 i = 5 时:
10 = 2 * 5,已经被2筛过15 = 3 * 5,已经被3筛过20 = 4 * 5,已经被2筛过
所以从 25 开始即可。
Python 代码实现
def sieve(n: int) -> list[int]: if n < 2: return []
is_prime = [True] * (n + 1) is_prime[0] = False is_prime[1] = False
for i in range(2, int(n ** 0.5) + 1): if is_prime[i]: for j in range(i * i, n + 1, i): is_prime[j] = False
return [i for i in range(2, n + 1) if is_prime[i]]
if __name__ == "__main__": print(sieve(50))输出结果:
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47]代码解析
1. 初始化素数数组
is_prime = [True] * (n + 1)is_prime[0] = Falseis_prime[1] = False这里用 is_prime[i] 表示数字 i 是否为素数。
一开始先假设所有数都是素数,然后再把 0 和 1 标记为非素数。
2. 遍历可能的因子
for i in range(2, int(n ** 0.5) + 1):外层循环只需要遍历到 sqrt(n)。
如果一个合数存在大于 sqrt(n) 的因子,那么它一定也存在一个小于等于 sqrt(n) 的因子。因此,只要筛到平方根即可。
3. 标记倍数
for j in range(i * i, n + 1, i): is_prime[j] = False如果 i 是素数,就将 i 的倍数全部标记为非素数。
这里从 i * i 开始,是因为更小的倍数已经在之前被筛过了。
4. 收集答案
return [i for i in range(2, n + 1) if is_prime[i]]最后遍历 is_prime 数组,把仍然为 True 的数字收集起来,就是 1 ~ n 中所有的素数。
复杂度分析
埃氏筛的时间复杂度是:
O(n log log n)空间复杂度是:
O(n)相比逐个判断素数的做法,埃氏筛在需要求出大量素数时效率非常高。
常见细节
1. 0 和 1 不是素数
素数定义是大于 1,并且只能被 1 和自身整除的整数。
因此:
is_prime[0] = Falseis_prime[1] = False是必要的。
2. 外层循环只需要到 sqrt(n)
写成:
for i in range(2, int(n ** 0.5) + 1):即可。
如果不想使用浮点数,也可以写成:
i = 2while i * i <= n: ... i += 13. n < 2 时直接返回空列表
因为小于 2 的范围内不存在素数:
if n < 2: return []总结
埃氏筛是求素数问题中非常经典的算法。
它不是一个一个判断每个数是不是素数,而是反过来利用素数去筛掉它的倍数。这个思想非常重要:通过批量排除合数,快速得到所有素数。
适合使用埃氏筛的场景包括:
- 求
1 ~ n中所有素数 - 统计素数个数
- 预处理素数表
- 多次查询某个数是否为素数
如果题目中出现“大范围内求素数”或“多次判断素数”,埃氏筛通常就是首选方案。