☰
线性筛法:高效素数筛选算法原理与实现
2026/10/31 5:37:53 网站建设 项目流程

1. 素数基础概念与常规判断方法

素数(质数)是指大于1的自然数中,除了1和它本身外不再有其他因数的数。理解素数的基本性质是掌握筛法的基础。我们先看一个简单的素数判断函数:

def is_prime(n): if n <= 1: return False for i in range(2, int(n**0.5)+1): if n % i == 0: return False return True

这个函数的时间复杂度是O(√n),对于单个数的判断尚可接受,但当我们需要找出一定范围内的所有素数时,这种方法的效率就显得不足了。例如要找出1到10^6之间的所有素数,这种暴力方法需要进行约10^9次运算。

2. 埃拉托斯特尼筛法原理与实现

埃拉托斯特尼筛法(简称埃氏筛)是最古老的素数筛选算法,其核心思想是:

从2开始,将每个素数的倍数都标记为合数,剩下的就是素数

具体实现步骤:

  1. 初始化一个布尔数组is_prime[0..n],全部设为True
  2. 从2开始遍历到√n:
    • 如果is_prime[i]为True,则i是素数
    • 将i的所有倍数(从i*i开始)标记为False
  3. 最后所有is_prime[i]为True的i就是素数

Python实现:

def eratosthenes(n): is_prime = [True] * (n+1) is_prime[0] = 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, prime in enumerate(is_prime) if prime]

埃氏筛的时间复杂度是O(n log log n),空间复杂度O(n)。虽然比暴力方法高效,但仍存在重复标记的问题——例如数字6会被2和3都标记一次。

3. 线性筛法的核心思想与优势

线性筛法(欧拉筛)通过确保每个合数只被其最小质因数标记一次,将时间复杂度优化到真正的O(n)。其核心原理是:

  1. 维护一个质数列表primes和标记数组is_prime
  2. 对于每个数i,无论它是否是素数,都用已知的质数primes[j]去标记i*primes[j]为合数
  3. 当i能被primes[j]整除时立即终止内层循环

这种方法的精妙之处在于:

  • 每个合数n只会被其最小质因数p标记(当i=n/p时)
  • 每个数只被标记一次,避免了重复计算
  • 同时还能记录每个数的最小质因数

4. 线性筛法的完整实现与解析

下面是线性筛法的Python实现:

def linear_sieve(n): is_prime = [True] * (n+1) primes = [] for i in range(2, n+1): if is_prime[i]: primes.append(i) for p in primes: if i * p > n: break is_prime[i * p] = False if i % p == 0: # 关键点:保证只被最小质因数标记 break return primes

关键点解析:

  • if i % p == 0这一判断确保了每个合数只被其最小质因数标记
  • 例如当i=4时,只会标记4×2=8,而不会标记4×3=12(因为12会被i=6时用2标记)
  • 内层循环的条件i * p <= n控制了标记范围

时间复杂度分析:

  • 外层循环执行n次
  • 内层循环中,每个合数只被标记一次
  • 因此总操作次数是O(n)

5. 线性筛法的应用与性能对比

线性筛法不仅高效,还能衍生出一些有用的副产品:

  1. 最小质因数表:可以记录每个数的最小质因数
def linear_sieve_with_spf(n): spf = [0] * (n+1) # smallest prime factor primes = [] for i in range(2, n+1): if spf[i] == 0: # i是素数 spf[i] = i primes.append(i) for p in primes: if p > spf[i] or i * p > n: break spf[i * p] = p return primes, spf
  1. 性能对比(n=10^6):

    • 暴力方法:约2秒
    • 埃氏筛:约0.3秒
    • 线性筛:约0.15秒
  2. 实际应用场景:

    • 需要频繁查询素数或质因数分解
    • 密码学中的素数生成
    • 数论问题求解

6. 线性筛法的优化技巧与边界处理

在实际应用中,我们可以对线性筛法进行一些优化:

  1. 内存优化:使用位图代替布尔数组
import array def linear_sieve_bit(n): is_prime = array.array('B', [1]) * ((n+7)//8) primes = [] def get_bit(x): return (is_prime[x//8] >> (x%8)) & 1 def set_bit(x): is_prime[x//8] &= ~(1 << (x%8)) for i in range(2, n+1): if get_bit(i): primes.append(i) for p in primes: if i * p > n: break set_bit(i * p) if i % p == 0: break return primes
  1. 分段筛法:处理超大范围的素数筛选

    • 将区间分成适合内存的小块
    • 用基础筛法预处理小素数
    • 对每个块用预处理的小素数进行筛选
  2. 常见问题处理:

    • 边界条件:n=0,1,2等特殊情况
    • 大数处理:注意数值溢出问题
    • 并行优化:多线程分段处理

7. 线性筛法在算法竞赛中的应用实例

例1:快速质因数分解

def factorize(x, spf): factors = {} while x > 1: p = spf[x] while x % p == 0: factors[p] = factors.get(p, 0) + 1 x = x // p return factors

例2:欧拉函数计算

def euler_phi(n, spf): result = n while n > 1: p = spf[n] result = result // p * (p-1) while n % p == 0: n = n // p return result

例3:素数计数函数π(n)

def prime_count(n): if n < 2: return 0 primes = linear_sieve(n) return len(primes)

这些应用展示了线性筛法不仅是一个高效的素数生成工具,还是许多数论算法的基础构件。在算法竞赛中,预处理素数表往往是解决数论问题的第一步。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询