1. 概述

素数一直是数学和计算机科学中非常有趣的研究对象。尽管数学家们一直试图找到一个简洁、有限的公式来生成所有素数,但至今仍未成功。因此,人们依赖于算法和计算能力来寻找素数。这些算法有的效率较高,有的则相对耗时。

本文将介绍一些经典的查找素数的算法,从最古老的到较新的方法逐一讲解。

2. 埃拉托色尼筛法(Sieve of Eratosthenes)

Sieve of Eratosthenes 是查找小于等于 n 的所有素数的最古老且简单有效的方法之一。

其核心思想是:从 2 开始,将每一个素数的所有倍数标记为合数。这样逐步筛去合数,剩下的就是素数。

原理详解

  1. 初始化一个大小为 n 的布尔数组 A,初始值为 true(表示是素数)。
  2. 从 2 开始,遍历到 √n:
    • 如果 A[i] 为 true,则 i 是素数。
    • 从 i² 开始,将所有 i 的倍数标记为 false(即合数)。
  3. 最终,数组中值为 true 的下标即为所有小于 n 的素数。

⚠️ 为什么从 i² 开始?
因为在遍历到 i 时,i 的所有比 i 小的倍数已经被前面的素数筛掉了。例如,当 i=7 时,49 是第一个未被筛掉的 7 的倍数。

算法伪代码

algorithm FindPrimesEratosthenes(n):
    A <- new boolean[n] initialized to true
    for i from 2 to sqrt(n):
        if A[i] is true:
            j <- i * i
            while j <= n:
                A[j] <- false
                j <- j + i
    return indices where A[i] is true

时间复杂度分析

  • 外层循环运行 O(√n) 次。
  • 内层循环对每个素数 i 来说,运行次数为 O(n / i)。
  • 总时间复杂度为:

$$ O(n \log \log n) $$

这是目前已知最高效的基础筛法之一。


3. 桑达拉姆筛法(Sieve of Sundaram)

Sundaram 筛法也是一种筛法,但它只筛掉特定形式的合数,最终通过变换得到所有奇素数。

核心思想

  1. 找出所有形如 i + j + 2ij 的数(其中 1 ≤ i ≤ j,且 i + j + 2ij ≤ n),并筛掉它们。
  2. 剩下的数乘以 2 再加 1,得到所有小于 2n+1 的奇素数。
  3. 注意:这个方法不会生成 2,因此需要手动添加。

算法伪代码

algorithm FindPrimesSundaram(n):
    k <- floor((n - 1) / 2)
    A <- new boolean[k + 1] initialized to true
    for i from 1 to sqrt(k):
        j <- i
        while i + j + 2 * i * j <= k:
            A[i + j + 2 * i * j] <- false
            j <- j + 1
    T <- indices where A[i] is true
    T <- 2 * T + 1
    add 2 to T
    return T

时间复杂度分析

  • 外层循环运行 O(√n) 次。
  • 内层循环平均运行 O(n / i) 次。
  • 总时间复杂度为:

$$ O(n \log n) $$

虽然比 Eratosthenes 筛法慢,但其思想独特,适合教学和理解。


4. 阿特金筛法(Sieve of Atkin)

Atkin 筛法是目前已知渐进最快的素数筛法之一,但实现复杂度高。

核心思想

  • 利用特定的二次方程解来标记可能的素数。
  • 通过模 60 分类处理,避免重复标记。
  • 最后筛除平方数的倍数,并返回结果。

算法伪代码(简化)

algorithm FindPrimesAtkin(n):
    A <- new boolean[n] initialized to false
    // 根据不同二次方程标记候选素数
    for x from 1 to sqrt(n):
        for y from 1 to sqrt(n) step 2:
            m = 4*x^2 + y^2
            if m <= n and (m mod 60 in {1, 13, 17, 29, 37, 41, 49, 53}):
                A[m] = not A[m]
    // 类似处理其他方程...
    // 筛除平方数的倍数
    for m from 2 to sqrt(n):
        if A[m] is true:
            for multiple from m^2 to n step m^2:
                A[multiple] = false
    // 添加 2, 3, 5
    return [2, 3, 5] + indices where A[i] is true

时间复杂度分析

  • 通过巧妙的数学方法和分类处理,Atkin 筛法的理论时间复杂度为:

$$ O(n) $$

优点: 渐进最优,适合大规模素数生成。
缺点: 实现复杂,常数因子较大,小规模数据反而慢。


5. 总结

算法名称 时间复杂度 实现难度 适用场景
埃拉托色尼筛法 O(n log log n) ✅ 简单 中小规模数据
桑达拉姆筛法 O(n log n) ✅ 简单 教学/理解
阿特金筛法 O(n) ❌ 复杂 大规模数据

📌 选择建议:

  • 数据量小或教学用途:使用 Eratosthenes 筛法。
  • 理解筛法思想:Sundaram 是个好例子。
  • 高性能需求、大规模数据:考虑 Atkin 筛法(注意其实现复杂度)。

📌 踩坑提醒:

  • 实现筛法时注意边界条件,尤其是数组索引和终止条件。
  • Atkin 筛法虽然理论复杂度最优,但实际中不一定最快,尤其是在 n 不太大时。

如需进一步优化性能,可考虑分段筛法(Segmented Sieve)或并行化实现。


原始标题:Fastest Algorithm to Find Prime Numbers