深入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 语言中的埃拉托斯特尼筛法!

相关推荐
weixin_307779131 小时前
C++代码实现MATLAB中的fitnlm函数功能
开发语言·c++·算法·matlab
雪落漂泊2 小时前
C++算法:双指针篇(下)
数据结构·c++·算法
白帽攻防录10 小时前
SRC 挖洞:PostScript 打印机驱动双 RCE,打印服务器怎么变成内网跳板
开发语言·网络安全·php·内网安全
十年Java程序媛10 小时前
Java枚举Enum完整教程|替代静态常量,开发与面试要点
java·开发语言·spring boot·后端·面试
2603_9651481111 小时前
电商选品调研:通过关键词搜索API获取市场热销商品,结合商品详情API分析竞品卖点与定价
开发语言·数据库·人工智能·python·信息可视化
生态学者11 小时前
Global Change Biology | 森林越“步调一致”,反而越怕干旱?树木生长与叶片活动的同步之谜
大数据·人工智能·算法·r语言·微信公众平台
xixiaoyunya11 小时前
Python 异步编程入门:用 asyncio 轻松实现并发任务
开发语言·python·php
lsswear11 小时前
Agent 框架新手入门与实战部署指南
大数据·人工智能·算法
学逆向的12 小时前
C 循环结构的执行效率分析
c语言·开发语言·数据结构·网络安全
小坏讲微服务12 小时前
刚发布一个月的JDK 27,为什么我建议你立刻升级?
java·开发语言·spring boot·spring·架构·求职招聘·springboot4