当前位置: 首页 > news >正文

质数筛(循环遍历,埃氏筛法,欧拉筛法)

参考文链接

1、循环遍历(最基础)

public static boolean isPrime(int num) {
    if (num <= 1) {
        return false;
    }
    // 一定是 <= 号
    for (int i = 2; i <= Math.sqrt(num); i++) {//使用Math.sqrt(num)减少遍历次数
        if (num % i == 0) {
            return false;
        }
    }
    return true;
}

该方法对于num小于等于1的情况直接返回false,因为1不是质数。

这种方法的时间复杂度为O(n*sqrt(n)) ,在n的值很大的时候效率较低

2、埃氏筛法(埃拉托斯特尼筛法)

埃氏筛法的基本思路是先把从2开始的所有数写下来,然后从2开始,将每个质数的倍数都标记成合数,即非质数,直到筛完所有小于等于给定数n的数。这样,留下的就是小于等于n的质数。

public static List<Integer> getPrimes(int n) {
// 为了下标和n对应,数组长度为n + 1
boolean[] isComposite = new boolean[n + 1]; // 标记数组,false表示该下标对应的数是质数
List<Integer> primes = new ArrayList<>(); // 用动态数组ArrayList存储质数
 
for (int i = 2; i <= n; i++) {
    if (!isComposite[i]) { // 如果该数是质数
        primes.add(i); // 把该数加入质数列表
        /* 常规思路是从 i * 2 开始,但是这样会重复判断多次
           从 i * i 开始遍历,可以略过很多次判断,因为只有
           从i的平方开始才不会和前面重复,略过了与前面数字
           的公倍数
           比如i是10,从10*10开始,避免了10*9(9的时候已经判断过了9*10),10*8。。。。。这些重复的判断
        */
        for (int j = i * i; j <= n; j += i) { // 标记该数的倍数为合数
            isComposite[j] = true;
        }
    }
}
return primes;

}
埃氏筛法时间复杂度为O(nloglogn), 此算法会重复筛,如i=2时已经判断过12,但i=3时仍然需要再次判断,增加了时间复杂度,此算法依然有优化的空间

for (int j = i * i; j <= n; j += i)已经是优化后的埃氏筛法,时间复杂度为O(nloglogn)

for(int j = 2 * i; j <= n; j += i)为优化前的埃氏筛法,时间复杂度为O(nloglog2n)

3、欧拉筛法(线性筛)

欧拉筛法的基本思路是将每个数表示为质数的乘积,然后按照质数的倍数依次筛选,这样每个合数只会被筛选一次,大大减少了时间复杂度

public static List<Integer> eulerSieve(int n) {
boolean[] isPrime = new boolean[n + 1]; // 标记数组,false表示该下标对应的数是质数
List<Integer> primes = new ArrayList<>(); // 用动态数组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++) {
        // 将已有质数的i倍标记为合数
        isPrime[i * primes.get(j)] = true;
        // 关键 当i能整除的时候就跳出循环(困惑点)
        if (i % primes.get(j) == 0) {
            break;
        }
    }
}
// primes列表中存储的就是所有小于等于n的质数
return primes;
}

**欧拉筛的核心思想就是确保每个合数只被最小质因数筛掉。或者说是被合数的最大因子筛掉。**时间复杂度是O(n)

困惑点

最难理解的应该就是

        if (i % primes.get(j) == 0) {
            break;
        }

这一步了。

我的理解:

i % primes.get(j) == 0 说明 i primes.get(j) 的倍数,说明此时的i为合数并且可以表示为 primes.get(j) 这一质数的倍数;( 也就是 i = primes.get(j) * ( i / primes.get(j) )

如果此时不跳出循环就会 在下一次循环中primes.get(j+1) * primes.get(j) * ( i / primes.get(j) ) 这个数给筛掉;(为便于理解这里的j+1表示下一次循环)

后续,当i=现在的 primes.get(j+1) * ( i / primes.get(j) ) 就又会和质数中的 primes.get(j) 配合筛掉和之前相同的数。

  • 这段代码保证的就是确保每个合数只被最小质因数筛掉

可以再换个方式理解:

首先我们要知道primes数组中的的质数是逐步递增

i % primes.get(j) == 0 说明i可以拆分成一个质数 primes.get(j) 和另一个数的乘积,我们都知道一个质数的倍数是一个合数,如果此时不跳出内层循环,就会出现这个合数不是被最小的质数筛掉的情况(因为i中包含了更小的质数,我们可以在后续循环中使用更大的i来筛)

举一个例子:

1 ,2,3,4,5,6,7,8,9,10,11, 12
i == 4时 :primes = {2, 3}
此时 i % 2 == 0 如果不结束内层循环的话, 12会被3 ∗ 4筛掉, 当i == 6时,12又会被2 ∗ 6筛掉。

3*4可以看成3*2*2也就是6*2,会出现不是最小质因数筛掉,从而出现重复筛。

相关文章:

  • 多线程编程:提高程序效率与响应性
  • Plusar集群搭建-Ubuntu20.04-Winterm
  • 容器化技术
  • AJAX简介
  • 漫画 Coco AI——打造跨平台、定制化搜索与 AI 智能问答体验
  • tradingview 隐藏按钮,隐藏菜单,
  • QML面试笔记--UI设计篇05容器控件
  • PG:incorrect prev-link
  • Tomcat 负载均衡
  • [实战] 天线阵列波束成形原理详解与仿真实战(完整代码)
  • oracle 12c密码长度,复杂度查看与设置
  • android14 keycode 上报 0 解决办法
  • 微软模拟飞行2004快速操作手册
  • 快瞳犬种识别效果图示,120种狗品种精准覆盖
  • MATLAB的24脉波整流器Simulink仿真与故障诊断
  • SU-YOLO:基于脉冲神经网络的高效水下目标检测模型解析
  • Git入门篇
  • 02-阿里云与HTTP协议
  • c++进阶之----c++11(可变参数模板)
  • 学习计划:从MCP入门到项目构建的全面指南
  • 巴基斯坦外长:印巴停火
  • 国家统计局:4月份居民消费价格同比下降0.1%
  • 巴基斯坦关闭全部领空
  • 巴基斯坦称对印度发起军事行动
  • 红场阅兵即将开始!中国人民解放军仪仗队亮相
  • 图忆|红场阅兵:俄罗斯30年来的卫国战争胜利日阅兵式