深入C语言底层系列28-埃拉托斯特尼筛法

目录

算法核心思想

[C 语言实现代码及详细注释](#C 语言实现代码及详细注释)

关键点解释

算法可视化


​​埃拉托斯特尼筛法(Sieve of Eratosthenes)​ ​是一种非常古老且高效的算法,用于找出一定范围内所有的素数。

算法核心思想

算法的核心思想非常巧妙:从 2 开始,将每个​​素数​ ​的​​所有倍数​ ​标记为​​非素数​​(合数)。

  1. ​​创建列表​ ​:首先创建一个从 2到 n的连续整数列表。

  2. ​​选取素数​ ​:选取第一个未被标记的数 p(它一定是素数)。当我们找到第一个未被标记的数 p(即 sieve[p]为 True)时,它一定是素数,因为如果它是合数,它必定有小于 p的质因子。但由于我们从 2 开始逐步标记倍数,任何小于 p的质因子都已经处理过,并标记了 p的倍数(包括 p本身)为合数。因此,如果 p未被标记,说明没有小于 p的质因子能标记它,所以 p是素数。

  3. ​​标记倍数​ ​:将 p的所有倍数(2p, 3p, 4p, ...)标记为合数。

  4. ​​重复步骤​ ​:重复步骤 2 和 3,直到 p² > n。

  5. ​​得到结果​​:所有未被标记的数即为素数。


C 语言实现代码及详细注释

复制代码
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <stdbool.h> // 为了使用 bool 类型

unsigned long long sieve(unsigned long long size) {
    // 1. 动态申请一个布尔数组,用于标记下标对应的数是否为素数
    // sieve[i] 为 true 表示数字 i 是素数
    bool *sieve = (bool*)malloc((size + 1) * sizeof(bool));
    if (sieve == NULL) {
        printf("Memory allocation failed!\n");
        exit(1);
    }

    // 2. 初始化数组,假设所有数初始都是素数
    for (unsigned long long i = 0; i <= size; i++) {
        sieve[i] = true;
    }

    // 0 和 1 不是素数,手动标记为 false
    sieve[0] = false;
    sieve[1] = false;

    // 3. 核心算法:埃拉托斯特尼筛法
    // 只需遍历到 sqrt(size) 即可
    for (unsigned long long i = 2; i * i <= size; i++) {
        // 如果 i 是素数(未被标记为合数)
        if (sieve[i] == true) {
            // 从 i*i 开始,将 i 的所有倍数标记为合数
            // (因为比 i*i 小的倍数已经被更小的素数标记过了)
            for (unsigned long long j = i * i; j <= size; j += i) {
                sieve[j] = false;
            }
        }
    }

    // 4. 统计素数的个数
    unsigned long long count = 0;
    for (unsigned long long i = 2; i <= size; i++) {
        if (sieve[i] == true) {
            count++;
        }
    }

    // 5. 释放动态申请的数组内存
    free(sieve);

    return count;
}

int main() {
    unsigned long long n = 100000000000; // 100亿
    unsigned long long result = sieve(n);
    printf("The number of primes less than %llu is: %llu\n", n, result);
    return 0;
}

注意:编译此代码时需要链接数学库,例如使用 GCC 编译器:gcc -o sieve sieve.c -lm


关键点解释

  1. ​​动态内存分配​​:

    • 由于 n可能非常大(如 100 亿),我们不能使用静态数组(如 bool sieve[100000000001]),因为这会在栈上分配内存,导致栈溢出。

    • 使用 malloc在​​堆​​上申请内存,堆的空间远大于栈。

  2. ​​优化:外层循环条件 i * i <= size​​:

    • 这是一个关键优化。如果一個数 size是合数,那么它一定有一个小于或等于 sqrt(size)的质因数。

    • 因此,我们只需要用 [2, sqrt(size)]范围内的素数去标记它们的倍数,就能确保所有合数都被标记出来。这大大减少了循环次数。

  3. ​​优化:内层循环从 i * i开始​​:

    • 对于素数 i,比 i*i小的倍数(如 i*2, i*3, ..., i*(i-1))已经被比 i更小的素数(如 2, 3, ...)标记过了。

    • 从 i*i开始标记避免了大量的重复工作。

  4. ​​空间与时间的权衡​​:

    • 该算法是一种典型的"以空间换时间"的算法。我们需要一个大小为 n+1的数组,但换来了 O(n log log n)的时间复杂度,这非常高效。

算法可视化

假设我们要找出 n=20以内的所有素数,其过程如下所示。图表清晰地展示了筛选的每一步,从2开始,依次标记其倍数,直到所有合数都被标记出来。

希望这个详细的解释能帮助你完全理解 C 语言中的埃拉托斯特尼筛法!

相关推荐
zjxtxdy24 分钟前
C++面向对象程序开发-类与对象
开发语言·c++
東隅已逝,桑榆非晚2 小时前
算法6. 双指针法-查找总价格为目标的两个商品
c++·学习·算法
Eyfcom2 小时前
〖物联网终端实战 01〗UWB 室内高精度定位:仓储 AGV 与人员的厘米级轨迹追踪
物联网·算法·物流系统·快递系统·跨境物流快递系统·物联网终端实战
qq_406981033 小时前
c++拷贝构造函数
开发语言·c++
bro_Java6663 小时前
《排序的底层逻辑:比较、交换与分治》
java·算法·排序算法
爱喝雪碧的可乐3 小时前
MATLAB
开发语言·算法·matlab
叼烟扛炮3 小时前
Linux 学习笔记(六十):单线程的逆袭——epoll 高并发服务器实战
linux·服务器·网络·笔记·学习·算法
careathers4 小时前
【数据结构】基于比较的排序算法 - 交换排序
数据结构·算法·排序算法
HUANGZHENXUAN1234 小时前
最大子矩阵和:从 O(n⁴) 到 O(n³) 的列压缩 + Kadane
c语言·前端
H.莓飛5 小时前
【C++】【STL】vector_OJ题
linux·服务器·开发语言·数据结构·c++·visual studio