在 C++ 里,为了找出一定范围内的所有素数,埃氏筛法(埃拉托斯特尼筛法)和线性筛法(欧拉筛法)是两种常用的算法。下面为你详细总结这两种算法。

1. 素数定义
素数,也叫质数,是指一个大于 1 的自然数,除了 1 和它自身外,不能被其他自然数整除的数。例如 2、3、5、7 等都是素数。

2. 埃氏筛法(埃拉托斯特尼筛法)
原理
该算法的核心思想是从 2 开始,将每个素数的倍数标记为合数,逐步筛选出所有素数。具体来说,先假设所有数都是素数,然后从最小的素数 2 开始,把 2 的倍数(除 2 本身)都标记为合数;接着找到下一个未被标记的数,它就是素数,再把它的倍数标记为合数,依此类推,直到遍历完所有小于等于给定范围的数。

代码实现

#include <iostream>
#include <vector>

// 埃氏筛法求素数
std::vector<int> sieveOfEratosthenes(int n) {
    std::vector<bool> isPrime(n + 1, true);
    isPrime[0] = isPrime[1] = false;  // 0 和 1 不是素数

    for (int i = 2; i * i <= n; ++i) {
        if (isPrime[i]) {
            for (int j = i * i; j <= n; j += i) {
                isPrime[j] = false;
            }
        }
    }

    std::vector<int> primes;
    for (int i = 2; i <= n; ++i) {
        if (isPrime[i]) {
            primes.push_back(i);
        }
    }

    return primes;
}

int main() {
    int n = 30;
    std::vector<int> primes = sieveOfEratosthenes(n);

    std::cout << "Prime numbers up to " << n << " are: ";
    for (int prime : primes) {
        std::cout << prime << " ";
    }
    std::cout << std::endl;

    return 0;
}

复杂度分析

  • 时间复杂度:约为 O(n log log n)。这是因为对于每个素数 p,需要标记 n/p 个数为合数,对所有素数求和得到时间复杂度。
  • 空间复杂度:O(n),主要用于存储标记数组。

优缺点

  • 优点:实现简单,易于理解,对于较小范围的素数筛选效率较高。
  • 缺点:会有重复标记的情况,例如 6 会被 2 和 3 都标记一次,导致效率有一定损失。

3. 线性筛法(欧拉筛法)
原理

线性筛法在埃氏筛法的基础上进行了优化,保证每个合数只会被它的最小质因数筛去,从而避免了重复标记,实现了线性时间复杂度。具体做法是在遍历过程中,对于每个数 (i),都与已找到的素数表中的素数相乘,并标记乘积为合数,当 (i) 能被当前素数整除时就停止,这样可以确保每个合数只被其最小质因数筛一次。

代码实现

#include <iostream>
#include <vector>

// 线性筛法求素数
std::vector<int> eulerSieve(int n) {
    std::vector<bool> isPrime(n + 1, true);
    std::vector<int> primes;
    isPrime[0] = isPrime[1] = false;  // 0 和 1 不是素数

    for (int i = 2; i <= n; ++i) {
        if (isPrime[i]) {
            primes.push_back(i);
        }
        for (int j = 0; j < primes.size() && i * primes[j] <= n; ++j) {
            isPrime[i * primes[j]] = false;
            if (i % primes[j] == 0) {
                break;
            }
        }
    }

    return primes;
}

int main() {
    int n = 30;
    std::vector<int> primes = eulerSieve(n);

    std::cout << "Prime numbers up to " << n << " are: ";
    for (int prime : primes) {
        std::cout << prime << " ";
    }
    std::cout << std::endl;

    return 0;
}

复杂度分析

  • 时间复杂度:O(n),因为每个数只会被筛一次,避免了埃氏筛法的重复标记问题。
  • 空间复杂度:O(n),主要用于存储标记数组和素数表。

优缺点

  • 优点:时间复杂度为线性,效率高,尤其适用于大范围的素数筛选。
  • 缺点:代码实现相对复杂一些,理解起来有一定难度。

4. 总结

  • 埃氏筛法实现简单,对于较小范围的素数筛选足够高效,当对代码简洁性要求较高且范围不大时可优先选择。
  • 线性筛法时间复杂度更优,适合处理大范围的素数筛选问题,但实现相对复杂,在对性能要求极高的场景下使用更为合适。
Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐