【数论】线性筛质数

线性筛质数

在之前的一篇筛质数的文章中只解释了埃式筛质数的方法,没有解释线性筛质数的方法

我们先看一下线性筛质数的代码

【例题】

给定一个正整数 n,请你求出 1∼n 中质数的个数。

输入格式

共一行,包含整数 n。

输出格式

共一行,包含一个整数,表示 1∼n 中质数的个数。

数据范围

1≤n≤10610^6106

输入样例:
8
输出样例:
4
import java.io.*;
import java.util.*;
public class Main {
    static final int N = 1000010;
    static int prime[] = new int[N];
    static boolean st[] = new boolean[N];
    static int idx;
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        function(sc.nextInt());
        System.out.println(idx);
        sc.close();
    }
    public static void function(int n) {
        for(int i = 2; i<= n; i++) {
            //如果i不是合数,则i为质数
            if(!st[i]) {
                prime[idx++] = i;
            }
            //从小到大枚举质数,循环条件为prime[j] <= n / i的原因是在循环体里面用prime[j]筛掉prime[j] * i的质数
            for(int j = 0; prime[j] <= n / i; j++) {
                st[prime[j] * i] = true;
                if(i % prime[j] == 0) {
                    break;
                }
            }
        }
    }
}

在这个代码中st数组记录的是合数,prime数组记录的是质数

其中每次都是从小到大枚举质数,所以会有两种情况

  • 情况1 i % prime[j] == 0

    在这种情况下,prime[j]一定是质数

    假设存在一个比 prime[j] 更小的质因子 prime[k] 能整除 i ,那么在之前遍历到 prime[k] 时,就会因为 i 能被 prime[k] 整除而提前跳出内层循环,不会到 prime[j] 这一步,所以当 i % prime[j] == 0 时,prime[j]i 的最小质因子。

    因为 pj 是质数,且 pji 的最小质因子,对于 pj * i 这个数,显然 pj 是它的质因子。并且不存在比 pj 更小的质因子能同时整除 pji ,所以 pjpj * i 的最小质因子 。

  • 情况2 i % prime[j] != 0

    在这种情况下,prime[j]一定比i的最小质因子还小, 因为primes 数组里的质数是从小到大排列的。如果 i 存在比 prime[j] 小的质因子,那么在之前遍历到那个更小的质因子时,就会发现它能整除 i ,即 i 对那个更小的质因子取模为 0

    而且在这种情况下,prime[j]也一定是prime[j] * i的最小质因子

对于一个合数x ,我们假设已经确定了它的最小质因子prime[j]

在筛法的循环过程中,我们会从较小的数开始枚举i 。当枚举到(i = x / prime[j]) 时,此时(prime[j] * i = x) 。这是因为根据数的因数关系,如果prime[j] 是x 的最小质因子,那么x 可以表示为(x = prime[j] * k) (k 为整数 ),这里的k 就是(x / prime[j])

线性筛法(也称为欧拉筛)是一种高效的素数筛选算法,其核心思想是通过避免重复标记合数来提升性能。与传统的埃拉托斯特尼筛法不同,线性筛法确保每个合数仅被其最小的质因子筛除一次,从而实现接近线性的时间复杂度 $ O(n) $。 ### 原理 线性筛法通过维护一个质数列表(`primes`),并为每个当前数字 `i` 遍历该列表,将 `i` 与每个质数相乘得到合数 `val = i * primes[j]`。关键点在于,当 `i` 能被当前质数整除时(即 `i % primes[j] == 0`),停止继续遍历质数列表。这一条件确保了每个合数只会被其最小质因子筛除,避免了重复操作。 ### 实现步骤 1. 初始化一个布尔数组 `isComposite`,用于标记每个数是否为合数(`false` 表示素数,`true` 表示合数)。 2. 初始化一个数组 `primes`,用于存储已找到的素数。 3. 从 2 开始遍历到目标范围上限 `n`: - 如果当前数 `i` 未被标记为合数,则将其加入素数列表。 - 对于每个素数 `primes[j]`,计算 `val = i * primes[j]`,并将 `val` 标记为合数。 - 如果 `i % primes[j] == 0`,则跳出循环,防止重复标记。 ### JavaScript 实现示例 以下是一个基于 JavaScript 的线性筛法实现,用于找出小于等于 `n` 的所有素数: ```javascript function linearSieve(n) { const isComposite = new Array(n + 1).fill(false); // false 表示素数 const primes = []; for (let i = 2; i <= n; i++) { if (!isComposite[i]) { primes.push(i); // 如果未被标记,则为素数 } // 遍历已找到的素数列表 for (let j = 0; j < primes.length; j++) { const val = i * primes[j]; if (val > n) break; // 超出范围则跳出 isComposite[val] = true; // 标记为合数 if (i % primes[j] === 0) break; // 确保只被最小质因子筛除 } } return primes; } // 示例:找出小于等于 30 的所有素数 console.log(linearSieve(30)); // 输出: [2, 3, 5, 7, 11, 13, 17, 19, 23, 29] ``` ### 时间复杂度分析 线性筛法的时间复杂度为 $ O(n) $,其中 `n` 是目标范围上限。这是因为每个合数仅被其最小质因子筛除一次,避免了传统埃拉托斯特尼筛法中可能出现的重复标记问题。 ### 空间复杂度分析 线性筛法的空间复杂度为 $ O(n) $,主要用于存储布尔数组 `isComposite` 和素数列表 `primes`。 ### 与其他筛法的比较 线性筛法相较于埃拉托斯特尼筛法在时间效率上有明显优势,特别是在处理大范围数据时。埃拉托斯特尼筛法的时间复杂度为 $ O(n \log \log n) $,而线性筛法则接近线性时间复杂度 $ O(n) $。此外,线性筛法还能够用于生成质因数分解所需的最小质因子表,进一步扩展其应用场景。 ### 应用场景 线性筛法广泛应用于数论相关计算,如: - 快速计算一定范围内素数的个数。 - 对数字进行质因数分解。 - 在密码学中生成大素数,为加密算法提供必要的数学支持[^2]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值