以下是使用 埃拉托色尼筛法 找出 100 以内所有素数的 Python 实现代码:
def sieve_of_eratosthenes(n):
# 创建一个布尔列表,初始值为True
is_prime = [True] * (n + 1)
is_prime[0] = is_prime[1] = False # 0 和 1 不是素数
# 从2开始标记其倍数为非素数
for i in range(2, int(n**0.5) + 1):
if is_prime[i]:
# 将i的倍数标记为非素数
for j in range(i * i, n + 1, i):
is_prime[j] = False
# 返回所有素数
return [x for x in range(n + 1) if is_prime[x]]
# 找出100以内的所有素数
primes = sieve_of_eratosthenes(100)
print("100以内的素数:", primes)
输出结果
运行上面的代码后,你将得到以下输出:
100以内的素数: [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]
代码说明
- 布尔列表
is_prime
:用来标记每个数字是否为素数,初始值为True
。 - 标记非素数:从 2 开始,将所有
i
的倍数标记为False
。 - 优化:从
i^2
开始标记,因为比i
小的倍数在之前已经被标记过。 - 结果生成:遍历布尔列表,返回所有值为
True
的索引。
这种算法在范围较大的情况下也非常高效。
埃拉托色尼筛法的时间复杂度和空间复杂度分析
时间复杂度
- 对于每个素数 ( p ),我们将从 ( p^2 ) 开始,标记它的所有倍数为非素数。
- 标记的次数可以用以下公式估算:
[
\text{总操作次数} = n \left( \frac{1}{2} + \frac{1}{3} + \frac{1}{5} + \dots \right)
]
这里的分母是素数的倒数。这个和可以近似为:
[
n \times (\text{自然对数ln}(\ln(n)))
]
所以,埃拉托色尼筛法的 时间复杂度为 ( O(n \log(\log(n))) )。
空间复杂度
- 主体的数据结构是一个布尔数组,大小为 ( n+1 )。
- 因此,埃拉托色尼筛法的 空间复杂度为 ( O(n) )。
总结
- 时间复杂度:( O(n \log(\log(n))) )
- 空间复杂度:( O(n) )
对于范围较大的素数筛选,这种方法在效率和空间占用上具有很好的平衡。
将埃拉托色尼筛法与传统的遍历算法(检查每个数是否是素数)对比,可以从 时间复杂度 和 空间复杂度 两个角度分析:
传统遍历算法
算法步骤:
- 遍历从 ( 2 ) 到 ( n ) 的所有数。
- 对于每个数 ( x ),判断是否能被小于等于 ( \sqrt{x} ) 的数整除。
- 如果 ( x ) 不能被任何数整除,则它是素数。
时间复杂度
- 判断一个数是否是素数需要最多 ( O(\sqrt{n}) ) 的时间。
- 遍历 ( n ) 个数,因此总时间复杂度为:
[
O(n \sqrt{n})
]
对比埃拉托色尼筛法 ( O(n \log(\log(n))) ),传统遍历算法的效率低得多。
空间复杂度
- 传统遍历算法只需常量空间来存储当前数和可能的因子,因此空间复杂度为:
[
O(1)
]
这比埃拉托色尼筛法 ( O(n) ) 更节省空间。
埃拉托色尼筛法
时间复杂度:
[
O(n \log(\log(n)))
]
埃拉托色尼筛法通过一次性标记非素数,避免了重复判断,因此对于大范围的素数查找更高效。
空间复杂度:
[
O(n)
]
需要一个布尔数组来存储每个数是否为素数。
对比总结
算法 | 时间复杂度 | 空间复杂度 | 优缺点 |
---|---|---|---|
传统遍历算法 | ( O(n \sqrt{n}) ) | ( O(1) ) | 适用于小范围的素数判断,空间占用小,但时间复杂度高,效率低。 |
埃拉托色尼筛法 | ( O(n \log(\log(n))) ) | ( O(n) ) | 适用于大范围的素数查找,时间复杂度低,效率高,但需要额外的存储空间。 |
适用场景
- 小范围素数查找:传统遍历算法简单易实现且节省空间,更适合。
- 大范围素数查找:埃拉托色尼筛法效率更高,适用于需要高效计算的大规模素数问题。
直观对比(100 以内的素数)
- 传统遍历算法:
- 每个数从 ( 2 ) 遍历到其 ( \sqrt{x} ),重复计算较多。
- 埃拉托色尼筛法:
- 一次性标记非素数,避免重复判断,提高效率。