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

目录

算法核心思想

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

关键点解释

算法可视化


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

算法核心思想

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

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

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

相关推荐
JH30732 小时前
深入解析Tomcat类加载器:为何及如何打破Java双亲委派模型
java·开发语言·tomcat
爱吃煎蛋的小新2 小时前
C#语法回忆零散巩固(持续更新最新版)
java·开发语言·笔记·学习·算法·c#
CoovallyAIHub2 小时前
时隔 8 年,李飞飞领衔,CS231n 2025版来了!
深度学习·算法·计算机视觉
一只乔哇噻2 小时前
java后端工程师进修ing(研一版‖day48)
java·开发语言·学习
宇钶宇夕2 小时前
S7-200 SMART PLC 以太网通信详解:3 种方式从原理到实操
运维·算法·自动化
美团技术团队2 小时前
开源 | InfiniteTalk:无限长虚拟人视频生成的新范式
人工智能·算法
CoovallyAIHub3 小时前
硬件升级之外,苹果AI的沉默杀手锏:统一视觉大模型ATOKEN
深度学习·算法·计算机视觉
数据知道3 小时前
Go基础:Go语言中内存分配用 new 还是 make?什么情况下该用谁?
服务器·开发语言·算法·golang·go语言
Absinthe_苦艾酒3 小时前
golang基础语法(三)常量、指针、别名、关键字、运算符、字符串类型转换
开发语言·后端·go