[Java][算法]Java 埃氏筛法与欧拉筛法 素数筛

本文对比了埃拉托斯特尼筛法与欧拉筛法在素数筛选中的应用,详细解析了两种算法的实现过程及优化点。埃氏筛法通过剔除素数的倍数来寻找素数,而欧拉筛法则确保每个合数仅由其最小素因子筛出,从而提升效率。

原文地址:https://blog.youkuaiyun.com/tinydolphin/article/details/75208771

import java.util.Arrays;

/**
 * 埃拉托斯特尼筛法 VS 欧拉筛法(更优化)
 * 
 * @author TinyDolphin
 *
 */
public class Main {

    private static final int MAX_LENGTH_CHECK = 100000000;          // 亿
    private static final int MAX_LENGTH_PRIMELIST = 10000000;       // 千万

    private static boolean[] check = new boolean[MAX_LENGTH_CHECK]; // 存储标记
    private static int[] primeList = new int[MAX_LENGTH_PRIMELIST]; // 存储素数

    /**
     * 埃拉托斯特尼筛法(简称埃氏筛或爱氏筛):要得到自然数n以内的全部素数,必须把不大于 根号n 的所有素数的倍数剔除,剩下的就是素数。
     * 例如:给出要筛数值的范围n,找出以内的素数。
     * 解法:先用2去筛,即把2留下,把2的倍数剔除掉;再用下一个质数,也就是3筛,把3留下,把3的倍数剔除掉;接下去用下一个质数5筛,把5留下,把5的倍数剔除掉;不断重复下去......。
     * 
     * 时间复杂度:O(nloglogn) 
     * 不足之处:6 在 indexI = 2 时被标记,而在 indexI = 3 时,又被标记了一次。存在重复标记,有优化空间
     */
    private static void Eratosthenes(int num) {
        int count = 0;
        for (int indexI = 2; indexI <= num; indexI++) {
            // 当 indexI 不是被剔除的数时,将 indexI 留下
            if (!check[indexI]) {
                primeList[count++] = indexI;
            }
            // 剔除 indexI 的倍数
            for (int indexJ = indexI + indexI; indexJ <= num; indexJ += indexI) {
                check[indexJ] = true;
            }
        }
    }

    /**
     * 欧拉筛法:保证每个合数只会被它的最小质因数筛掉,时间复杂度降低到O(n)。 
     * 每一个数都去乘以当前素数表里面已有的数,当 indexI 是合数,且 indexI % primeList[indexJ] == 0 时,只能乘以第一个素数 2
     */
    private static void Euler(int num) {
        int count = 0;
        for (int indexI = 2; indexI <= num; indexI++) {
            if (!check[indexI]) {
                primeList[count++] = indexI;
            }
            // 每一个数都去乘以当前素数表里面已有的数,如果 indexI 是合数,且 indexI % primeList[indexJ] == 0,那么它只能乘以第一个素数 2
            // 如:2×2、3×(2、3)、4×(2)、5×(2、3、5)、6×(2)、7×(2、3、5、7)、8×(2)、9×(2、3)、10×(2)
            for (int indexJ = 0; indexJ < count; indexJ++) {
                // 过大的时候跳出
                if (indexI * primeList[indexJ] > num) {
                    break;
                }
                check[indexI * primeList[indexJ]] = true;
                // 如果 indexI 是一个合数,而且 indexI % primeList[indexJ] == 0
                // 保证了每个合数只会被它的最小素因子筛掉
                if (indexI % primeList[indexJ] == 0) {
                    break;
                }
            }
        }
    }

    public static void main(String[] args) {
        int n = 100000; // 十万
        long start = System.currentTimeMillis();
        Eratosthenes(n);
        long end = System.currentTimeMillis();
        System.out.println("十万级别数量:埃氏筛,耗时:" + (end - start) + " ms");

        Arrays.fill(check, false);
        Arrays.fill(primeList, 0);

        start = System.currentTimeMillis();
        Euler(n);
        end = System.currentTimeMillis();
        System.out.println("十万级别数量:欧拉筛法,耗时:" + (end - start) + " ms");
    }

}

 

<think>好的,我需要解决用户关于埃氏筛法素数的例题和代码实现的请求。首先,我得回忆一下埃氏筛法的基本原理,确保自己理解正确。埃氏筛法是通过标记合数来素数,从2开始,将每个素数的倍数都标记为非素数,这样剩下的就是素数了。用户提到了几个引用内容,里面包含不同的代码示例和区间筛法的说明。我需要参考这些内容,但用户真正需要的是例题或代码实现。因此,我应该先给出一个标准的埃氏筛法代码,可能结合引用中的例子,比如引用[1]和[2]中的代码,但要注意用户可能希望看到的是C++或Java实现,不过用户提供的示例中有Python的快速排序,所以可能也需要Python的代码?不过根据引用中的代码,C++和Java的示例较多,用户的问题可能偏向于算法实现,所以应该选择常见的语言,比如C++或Python。接下来,我需要确保代码正确,符合埃氏筛法的逻辑。例如,初始化标记数组,将0和1标记为非素数,然后从2开始循环,如果当前数未被标记,则将其倍数全部标记为合数。引用[1]和[2]中的代码都是这样的结构,需要注意循环的起始点和步长是否正确。 然后,用户可能需要例题,比如如何用埃氏筛法求解某个范围内的素数,或者处理大区间的筛法(如引用[4]中的区间)。这里可能需要给出一个具体的例子,比如求n以内素数,或者解决某个问题,比如统计素数的数量,或者输出所有素数。另外,用户可能希望看到代码的解释,说明每一步的作用,这样他们可以更好地理解。例如,初始化数组,标记0和1,然后遍历每个数,如果是素数就标记其倍数。同时,时间复杂度的解释,埃氏筛法的时间复杂度是O(n loglog n),这可以作为补充说明。还需要注意用户提到的LaTeX格式要求,所有行内数学表达式要用$...$,比如时间复杂度写成$O(n\log\logn)$,而独立公式用$$...$$。例如,在解释算法复杂度时可能需要用到。可能还需要考虑例题的应用场景,比如解决LeetCode上的某个问题,例如LeetCode204题“计数素数”,这正是埃氏筛法的典型应用。可以给出这个例题的解决方案,结合代码实现,这样用户可以看到实际应用。另外,引用[4]提到的区间筛法可能是一个扩展内容,如果用户的问题涉及到大区间的素数选,可以简要提及,并提供相应的例题或代码。例如,求区间[L,R]内的素数,其中R很大,但区间长度较小的情况,这时候可以使用区间筛法,先出√R以内素数,再用这些素数区间内的合数。最后,生成相关问题需要考虑用户可能进一步的问题,比如埃氏筛法的优化(如引用中的代码是否有优化空间),时间复杂度分析,其他筛法的比较(如欧拉),或者错误处理(如输入范围过大时的内存问题)等。</think>### 埃氏筛法素数详解 #### 一、算法原理 埃氏筛法通过标记合数实现素数选。步骤如下: 1. 初始化标记数组`a[]`,默认所有数都是素数(`false`) 2. 将0和1标记为非素数(`true`) 3. 从2开始遍历,若当前数未被标记为合数,则将其所有倍数标记为合数 数学依据:任意合数$n$必有小于等于$\sqrt{n}$的质因数[^4] #### 二、标准代码实现(C++) ```cpp #include <iostream> #include <cstring> using namespace std; const int N = 1e7 + 5; // 根据问题规模调整 bool a[N]; // false表示素数 void eratosthenes(int n) { memset(a, 0, sizeof(a)); a[0] = a[1] = true; // 标记非素数 for(int i = 2; i*i <= n; i++) { // 优化:只需遍历到√n if(!a[i]) { for(int j = i*i; j <= n; j += i) // 从i²开始标记 a[j] = true; } } } ``` **时间复杂度**:$O(n \log \log n)$[^1] #### 三、典型例题 **LeetCode 204. 计数素数** 题目:统计所有小于非负整数n的质数的数量 解法: ```cpp class Solution { public: int countPrimes(int n) { if(n <= 2) return 0; vector<bool> isPrime(n, true); isPrime[0] = isPrime[1] = false; for(int i = 2; i*i < n; i++) { if(isPrime[i]) { for(int j = i*i; j < n; j += i) isPrime[j] = false; } } return count(isPrime.begin(), isPrime.end(), true); } }; ``` #### 四、区间筛法(处理超大范围) **例题**:求区间$[L, R]$内的素数,其中$1 \leq L \leq R \leq 10^{12}$且$R-L \leq 10^6$ ```cpp void interval_sieve(long long L, long long R) { vector<bool> isPrime(R-L+1, true); if(L == 0) isPrime[0] = false; if(L <= 1) isPrime[1-L] = false; // 先用普通筛法得到√R内的素数 int max_p = sqrt(R); vector<bool> sieve(max_p+1, true); for(int p=2; p<=max_p; p++){ if(sieve[p]){ for(long long j=max(2LL*p, (L+p-1)/p*p); j<=R; j+=p) isPrime[j-L] = false; for(int j=p*p; j<=max_p; j+=p) sieve[j] = false; } } // 输出结果 for(long long i=L; i<=R; i++) if(isPrime[i-L]) cout << i << endl; } ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值