线性筛法(大概可能通俗易懂)

2023年09月14日23:00发布markdown重写版本,此处不再更新,链接:markdown版+内容更新

2023年06月03日00:50第二次修改,修改部分措辞,部分内容重新排版

2023年05月12日12:24第一次修改,修改部分措辞,部分内容重新排版

目录

阅读之前

废话

不遗漏和不重复的证明

如何保证枚举最小质因子

代码实现


阅读之前

上下文结合观看更有助于理解,建议理解了简单一些的埃式筛后再进行阅读,本文主要讲解欧拉筛

本文直接或间接涉及到的一些数学概念:

        自然数:非负整数的集合. 即一个数大于等于0,且为整数

        质数:自然数中除了0,1以外只能被1和自身整除的数

        合数:自然数中除了0,1和质数以外的数

        因子:一个整数n能够正好整除一个整数m,则m为n的因子,但是自身不为自身的因子

        唯一分解定理:任何一个大于1的整数n都可以分解成若干个质因子的连乘积,如果不计各个质因子的顺序----比如6 = 2*3 = 3*2,如果计顺序那么有两种分解顺序,不计的话只有一种----那么这种分解是唯一的。由德国数学家高斯于1801年证明

废话

一般筛法 / 埃式筛:先定义一个大小为 maxn 的数组代表将要进行筛选的n个数,然后基于每个质数的两倍及更高倍数为非质数的思想对数组进行筛选,最后未筛选的数即为质数

线性筛法 / 欧拉筛:在一般筛法的基础上优化所得,在O(n)的复杂度内求从1到n的范围内的质数。避免了埃式筛对某些数字进行重复筛选的缺点,因此从O(n * log log n)优化到O(n)

结论:将每一轮进行筛选的数 n 表示为 i * prime[ j ] (即 n = i * prime[ j ])。保证 i 为 n 的最大因子,prime[ j ] 为 n 的最小因子(最小因子必为质数。如果不为质数,则该数可以分解为两个数的乘积, 与最小因子矛盾)

数 n 能被分解成有限个质数相乘,(在这有限个质数当中可能包含了相同的质数,比如300可以分解为2 * 2 * 3 * 5 * 5),将这有限个质数从小到大排列,取出其中最小的质数即为最小因子,再将剩下的数乘起来即为最大因子(比如300的最小因子为2,最大因子为150)

在筛选过程中将用到:

一个数表 num[maxn] 用于记录当前下标所代表的数是否为质数;

一个质数表 prime[maxn] 用于从小到大依次储存质数;

两个变量 i 和 j 用于建立双层循环,外层循环的 i 用于遍历 num 数组作为最大因子,内层循环的 j 用于遍历 prime 数组作为最小因子

主要问题在于如何筛选不遗漏、不重复,以及保证枚举最小因子

不遗漏和不重复的证明

每个合数必定可以用它的最小因子和最大因子的乘积表示出来而且唯一 (唯一分解定理),所以用双层循环从小到大依次枚举 i (最大因子) 和 prime[ j ] (最小因子) ,二者的乘积必定能枚举出所有的合数,做到不遗漏

因为每一次枚举的 i 和 prime[ j ] 的组合不一样,所以其乘积也不一样,筛除的合数也不一样, 保证了不重复

如何保证枚举最小因子

这样又带来了新的问题: 如何保证 prime[ j ] 就是最小质因子呢? 因为我们必须保证prime[ j ] 为 prime [ j ] * i 的最小因子, 才能满足不重复和不遗漏的要求

PS:同理,如果我们保证每次枚举的因子为最大因子,那么我们也能保证不重复、不遗漏,因为 i = n / prime[ j ],prime[ j ] 唯一,则该最大因子唯一,但是枚举最大因子不好实现,所以我们选择枚举最小因子

prime数组中所有小于等于 i 的最小质因子的质数prime[ j ] ( j = 0,1,2,3,~)和 i 相乘后都满足prime[ j ] 为 prime[ j ] * i 最小质因子,在prime[ j ] 等于 i 的最小因子时结束对 i 的筛选,可以保证枚举的prime[ j ] 为 prime[ j ] * i 的最小因子

证明(反证法):

假设枚举的最大因子 i = p3 * p4,如果 prime[ j ] 在 j 等于 4 时仍然没结束筛选,这时 n = p3 * p4 * p4,prime[ j ] = p4 非最小因子,这种情况下会与 prime[ j ] 在 j 等于3时,i = p4 * p4 时重复。所以,在prime[ j ] 等于 i 的最小因子时结束对 i 的筛选,可以保证枚举的prime[ j ] 为 prime[ j ] * i 的最小因子

比如:不能出现 i = 4, prime [ j ] = 3 乘积为 12 的组合, 因为 3 不为 12 的最小因子。如果不避免这种情况就会和 i = 6, prime[ j ] = 2 乘积也为 12 的组合重复,而后者为正确的筛除12的方式

代码实现

#define maxn (int)1e5

//全局变量自动初始化为0
bool num[maxn];

//1e5个数里质数数量肯定比1e5少的多, 这里随便除了个2优化一下空间
int prime[maxn / 2];
int i, j, cnt;

void Euler(void) {

    //初始用1标记2及以上的数全为质数. 也可以把0作为质数标记, 然后修改num0和num1为1
    for(i = 2; i < maxn; i++)
        num[i] = 1;

    //i为最大因子
    for(i = 2; i < maxn; i++) {

        //如果当前数未被筛除, 则加入质数表
        if (num[i])
            prime[cnt++] = i;

        //j遍历prime数组, prime[j]作为最小因子
        for (j = 0; i * prime[j] < maxn; j++){
            //筛除该数
            num[i * prime[j]] = 0;

            //当prime[j]为i的最小因子时退出for循环,
            //因为当prime[j]大于i的最小因子时,  
            //n的最小因子已经存在于i的分解式中了
            if (!(i % prime[j]))
                break;
        }
    }
}

封面出处:AcWing 1356. 回文质数 - AcWing

  • 10
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: 线性筛是一种常用的求素数的方,它的思想简单且高效。下面是使用Java语言实现线性筛求素数的代码: ```java import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class LinearSieve { public static List<Integer> getPrimes(int n) { boolean[] isComposite = new boolean[n + 1]; List<Integer> primes = new ArrayList<>(); for (int i = 2; i <= n; i++) { if (!isComposite[i]) { primes.add(i); } for (int j = 0; j < primes.size() && i * primes.get(j) <= n; j++) { isComposite[i * primes.get(j)] = true; if (i % primes.get(j) == 0) { break; } } } return primes; } public static void main(String[] args) { int n = 100; List<Integer> primes = getPrimes(n); System.out.println("从 2 到 " + n + " 的素数为:"); for (int prime : primes) { System.out.print(prime + " "); } } } ``` 以上代码中,我们使用了一个布尔数组`isComposite`来标记是否为合数。初始时,将所有数都标记为非合数。然后从2开始,遍历到n,如果某个数i是合数,则跳过;如果是素数,则将其加入到素数列表中,并标记它的倍数为合数。遍历结束后,我们得到了从2到n的所有素数。 在main函数中,我们设置n为100,调用`getPrimes`函数获取从2到100的素数,并打印出来。 运行结果为:从 2 到 100 的素数为:2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97。 ### 回答2: 线性筛是一种高效地筛选出一定范围内的素数的算法。下面是用Java实现线性筛求素数的代码: ```java import java.util.*; public class LinearSieve { public static List<Integer> sieve(int n) { boolean[] isPrime = new boolean[n + 1]; Arrays.fill(isPrime, true); // 将所有数初始化为素数 List<Integer> primes = new ArrayList<>(); for (int i = 2; i <= n; ++i) { if (isPrime[i]) { primes.add(i); // 将素数加入结果列表 } for (int j = 0; j < primes.size() && i * primes.get(j) <= n; ++j) { isPrime[i * primes.get(j)] = false; // 将当前素数倍数标记为非素数 if (i % primes.get(j) == 0) { break; // 若当前数为素数倍数,跳出内层循环 } } } return primes; } public static void main(String[] args) { int n = 100; // 范围上限 List<Integer> primes = sieve(n); System.out.println("范围[2, " + n + "]内的素数有:"); for (int prime : primes) { System.out.print(prime + " "); } } } ``` 通过线性筛,我们首先将所有数初始化为素数,然后从2开始,将每个素数的倍数标记为非素数,直到筛选结束。最后,将筛选出的素数存入结果列表中。在上述代码中,我们以100为例,调用`sieve`方求解范围内的素数,并输出结果。 当我们运行上述代码时,将会得到范围[2, 100]内的素数列表: ``` 范围[2, 100]内的素数有: 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97 ``` 以上就是使用Java实现线性筛求素数的代码及结果。 ### 回答3: 线性筛是一种用于求解素数的算法,可以高效地找出某一个范围内的所有素数。下面是使用Java语言实现线性筛求素数的代码: ```java import java.util.ArrayList; import java.util.List; public class PrimeNumbers { public static List<Integer> getPrimeNumbers(int n) { List<Integer> primeNumbers = new ArrayList<>(); boolean[] isComposite = new boolean[n + 1]; for (int i = 2; i <= n; i++) { if (!isComposite[i]) { primeNumbers.add(i); } for (int j = 0; j < primeNumbers.size() && i * primeNumbers.get(j) <= n; j++) { isComposite[i * primeNumbers.get(j)] = true; if (i % primeNumbers.get(j) == 0) { break; } } } return primeNumbers; } public static void main(String[] args) { int n = 100; List<Integer> primeNumbers = getPrimeNumbers(n); System.out.println("在[2, " + n + "]范围内的素数有:"); for (int number : primeNumbers) { System.out.println(number); } } } ``` 这段代码使用了一个布尔数组isComposite来记录某个数是否为合数(非素数),初始时假设所有数都是质数,然后从2开始遍历到n,如果某个数i没有被标记为合数,就将其添加到素数列表中,并将i与已有的质数依次相乘,将其标记为合数。 运行以上代码,可以求解出2到100之间的所有素数。输出结果如下: ``` 在[2, 100]范围内的素数有: 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97 ``` 通过线性筛,我们可以高效地找到某个范围内的素数,而不需要遍历所有的数进行判断。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值