线性筛

来自GSXAB的知识库
(重定向自Euler 筛法
线性筛
术语名称 线性筛
英语名称 linear seive
别名 欧拉筛法, 欧拉筛, 线性筛法

线性筛法(linear seive)或线性筛,也称欧拉,是找出小于某正整数的全部质数的算法。这一算法是 Eratosthenes 筛法的改进版,因时间复杂度降低到线性复杂度而得名。

改进原理

理论上判定所有数的下限是遍历一次所有的数,而 Eratosthenes 筛法的时间复杂度为 [math]\displaystyle{ O(n\log n\log n) }[/math] ,可以考虑额外的复杂度是如何引入的。可以发现,过程中删去每个质数的全部倍数时,在有不同质因数的合数上执行了两次删除,这使得很多数字被重复删除多次(部分优化中,只执行平方以上的筛选,减少了部分重复,但是对商大于某个质因数的情况还会重复,比如 [math]\displaystyle{ 12 = 2\times 6 = 3\times 4 }[/math] )。如果能保证每个数字只遍历一次,就可以得到线性复杂度的算法。

Euler 筛中在删除倍数的基础上进行了新的约束:每个数只被其最小质因数删除一次。

对任意一个数 [math]\displaystyle{ n = p_1^{n_1} p_2^{n_2} \cdots p_r^{n_r} }[/math] ,考虑在遍历其最小质因数 [math]\displaystyle{ p_1 }[/math] 时删除。但是对 [math]\displaystyle{ p_1 }[/math] 由于我们还没有找到 [math]\displaystyle{ p_2, \cdots, p_r }[/math] ,根本不知道哪些倍数是要删除的,只能调转一下思路。筛选时需要遍历所有整数,而不是所有质数。将其当作某个 [math]\displaystyle{ \tfrac{n}{p_1} = p_1^{n_1-1} p_2^{n_2} \cdots p_r^{n_r} }[/math] ,考虑所有可能的 [math]\displaystyle{ p_1 }[/math] 。注意这里存在 [math]\displaystyle{ n_1-1 =0 }[/math] 的情况,所以 [math]\displaystyle{ p_1 }[/math] 不一定是 [math]\displaystyle{ \tfrac{n}{p_1} }[/math] 的最小质因数,唯一能确认的是 [math]\displaystyle{ p_1 }[/math] 小于等于 [math]\displaystyle{ \tfrac{n}{p_1} }[/math] 的最小质因数。(因此流程上更像“每个数只被其与其最小质因数的商删除一次”,等价于被最小质因数删除了。)

因此需要额外记录每个数的最小质因数,用于判定其需要相乘的其他质数。显然由于每个数只被其最小质因数删除一次,在删除时记录下的最小质因数就是这里所需的最小质因数。

算法

列出所有正整数,删去 1 后,每次取出最前的数。

若这个数没有被删去,则自身是自身的最小质因数;若这个数被删去,则有最小质因数标记。删去其与全部小于等于其且未被删去的数的乘积。

直到所有的数都被遍历。

(规定范围时也可以遍历一半的数,因为之后的数乘以 2 不在筛选范围,不需要继续筛选)

举例

为了便于理解,这里将正整数 [math]\displaystyle{ n }[/math] 按照其质因数个数 [math]\displaystyle{ \Omega(n) }[/math] 分类:

  • [math]\displaystyle{ \Omega(1)=0 }[/math] ,即整数 1 : 1 是被直接删去的。
  • [math]\displaystyle{ \Omega(p)=1 }[/math] ,即全体质数:不含有 1 和本身以外的因数,因此不会被其他因数删去。
  • [math]\displaystyle{ \Omega(p_1 p_2)=2 }[/math] ,即全体双质数:不妨设 [math]\displaystyle{ p_1 \leq p_2 }[/math] ,则遍历到 [math]\displaystyle{ p_2 }[/math] 时,遍历更小的质数 [math]\displaystyle{ 2,3,5,\cdots,p_1,\cdots,p_2 }[/math] 时,循环中会删去 [math]\displaystyle{ 2p_2,3p_2,5p_2,\cdots,p_1p_2,\cdots,p_2^2 }[/math] ,因此会正确删去这个数并标记其最小质因数为 [math]\displaystyle{ p_1 }[/math]
  • [math]\displaystyle{ \Omega(p_1 p_2 p_3)=3 }[/math] :不妨设 [math]\displaystyle{ p_1 \leq p_2 \leq p_3 }[/math] ,则遍历到合数 [math]\displaystyle{ p_2 p_3 }[/math] 时,由于删去这个数时标记了其最小质因数为 [math]\displaystyle{ p_2 }[/math] ,那么会遍历更小的质数 [math]\displaystyle{ 2,3,5,\cdots,p_1,\cdots,p_2 }[/math] 时会删去 [math]\displaystyle{ 2p_2p_3,3p_2p_3,5p_2p_3,\cdots,p_1p_2p_3,\cdots,p_2^2p_3 }[/math] ,因此会正确删除这个数并标记其最小质因数为 [math]\displaystyle{ p_1 }[/math]
  • 如果对任意 [math]\displaystyle{ \Omega(n)=r-1 }[/math] 的成立,考虑对 [math]\displaystyle{ \Omega(n)=r }[/math] ,设其最小质因数为 [math]\displaystyle{ p_1 }[/math] ,由于 [math]\displaystyle{ \Omega(\tfrac{n}{p_1})=r-1 }[/math] ,算法已经正确删去并标出其最小质因数 [math]\displaystyle{ p_2 \geq p_1 }[/math] ,因此对于[math]\displaystyle{ \tfrac{n}{p_1} }[/math] 将遍历并删去 [math]\displaystyle{ \tfrac{2n}{p_1},\tfrac{3n}{p_1},\tfrac{5n}{p_1},\cdots,\tfrac{p_1 n}{p_1}=n,\cdots,\tfrac{p_2 n}{p_1} }[/math] ,正确删去 [math]\displaystyle{ n }[/math] 并标记其最小质因数为 [math]\displaystyle{ p_1 }[/math]

因此根据第一数学归纳法,可以证明对于所有有自然数个质因子的数,即全体正整数,这一算法都能够正确筛选出其中的质数。

伪代码

过程 线性筛
别名 linear_seive
参数 n : 正整数 // 范围上限
返回 primes : 正整数的列表 // 质数列表

令 p₁~ₙ 为 未删去
令 f₁~ₙ 为 未初始化(使用 0 或 1 等不冲突的数值标记) // 最小质因数

循环 k
当 k ≤ n 时 ❶
执行
    如果 k 未删去 那么
        令 fₖ ← k
    将 k 加入列表 primes
    记 q ← 2 // 遍历更小的质数
    循环 q 当 q ≤ fₖ 时
        如果 q 已删去 那么
            继续下一循环
        记 n ← kq
        令 pₙ 为 已删去
        令 fₙ 为 q
    继续循环 q ← q + 1
继续循环 k ← k + 1

返回 primes

❶ 这里可以改为 2k ≤ n

复杂度

主要操作是遍历正整数及部分倍数,分为两部分。作为 k 遍历时,由于内层循环较为复杂,取决于标记情况,又知道所有合数都仅被标记一次,将其求和得到的是范围内全部合数,近似于范围内的全体正整数,即 [math]\displaystyle{ O(n) }[/math]

情况 复杂度估计 渐近级别 发生条件
平均情况 [math]\displaystyle{ \sim n }[/math] [math]\displaystyle{ O(n) }[/math] -
最好情况
最坏情况


整除理论
整除关系 整除、倍数、因数 带余除法
正整数的分类 1质数、合数
质数测试 试除法埃氏筛线性筛
最大公约数理论 公倍数、最小公倍数 [math]\displaystyle{ \operatorname{lcm} }[/math]公因数、最大公因数 [math]\displaystyle{ \operatorname{gcd} }[/math] 辗转相除法
互质
算术基本定理 算术基本定理 标准质因数分解