Files
SolutionEuler/solutions/0000_0029/0007.10001stPrime
2025-12-26 17:35:14 +08:00
..
2025-12-26 17:35:14 +08:00
2025-12-26 17:35:14 +08:00

埃拉托斯特尼筛法

埃拉托斯特尼筛法Sieve of Eratosthenes是最古老、最优雅的质数筛选算法 由古希腊数学家埃拉托斯特尼在公元前3世纪提出。

核心思想

" multiples of primes are composite "
(质数的倍数都是合数)

从2开始逐个标记每个质数的所有倍数为非质数剩下的未标记数就是质数。


算法步骤

示例:找出 ≤30 的所有质数

  1. 初始化:列出 2 到 30 的所有整数

    2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
    
  2. 筛选过程

    • p=2保留2标记所有2的倍数4,6,8...
    • p=3下一个未标记的是3保留3标记3的倍数6,9,12...
    • p=5下一个未标记的是5保留5标记5的倍数10,15,20,25,30
    • p=77²=49 > 30停止
  3. 结果:剩下的未标记数

    2 3 5 7 11 13 17 19 23 29
    

关键优化点

只需筛到 √n
要找出 ≤n 的所有质数,只需检查到 √n 即可。
证明:任何合数 m ≤ n 必有一个质因数 ≤ √n否则 m = p₁×p₂ > √n×√n = n矛盾。


正确实现Python

def sieve(n):
    """返回所有小于等于n的质数"""
    if n < 2:
        return []
    
    sieve = [True] * (n + 1)
    sieve[0:2] = [False, False]  # 0和1不是质数
    
    for p in range(2, int(n**0.5) + 1):
        if sieve[p]:
            # 从p²开始标记更小的倍数已被前面的质数标记过
            sieve[p*p:n+1:p] = [False] * ((n - p*p) // p + 1)
    
    return [i for i, is_prime in enumerate(sieve) if is_prime]

复杂度分析

指标 复杂度 说明
时间 O(n log log n) 近乎线性,非常高效
空间 O(n) 需要布尔数组存储每个数

推导
质数p会标记 n/p 个数,总操作量 ≈ n×(1/2 + 1/3 + 1/5 + ... + 1/p_k)
根据质数定理,该和式 ≈ n log log n


进阶优化技巧

  1. 仅筛奇数偶数除了2都不是质数内存减半

    sieve = [True] * ((n + 1) // 2)
    # 索引i对应数字 2*i+1
    
  2. 位压缩用bit array而非bool内存再降8倍

    from bitarray import bitarray
    sieve = bitarray(n + 1)
    sieve.setall(True)
    
  3. 分段筛选当n极大>10⁸分段加载到缓存


适用场景

适合

  • 需要一次性生成大量质数
  • 频繁查询范围内的质数
  • n在百万到千万级别

不适合

  • 只需判断单个数是否为质数
  • n极大>10¹⁰且内存有限
  • 仅需要第n个质数而非全部

分段筛法当n极大>10⁸分段加载到缓存减少内存占用。

def segmented_nth_prime(n, segment_size=100000):
    # 先用小筛法生成基础质数
    base_limit = int((n * (log(n) + log(log(n))))**0.5) + 1
    base_primes = sieve_primes(base_limit)
    
    count = len(base_primes)
    if count >= n:
        return base_primes[n-1]
    
    low = base_limit
    while True:
        high = low + segment_size
        segment = [True] * segment_size
        
        for p in base_primes:
            start = ((low + p - 1) // p) * p
            if start < p * p:
                start = p * p
            for multiple in range(start, high, p):
                segment[multiple - low] = False
        
        for i, is_p in enumerate(segment):
            if is_p and i + low > 1:
                count += 1
                if count == n:
                    return i + low
        low = high

生产环境推荐使用 pyprimesieve 库

# 生产环境推荐使用 pyprimesieve 库
# pip install pyprimesieve
from pyprimesieve import nth_prime
print(nth_prime(10**7))