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

相关推荐
代码游侠6 小时前
应用--Minishell实现
linux·运维·笔记·学习·算法
m0_471199636 小时前
【vue】diff算法简介
前端·vue.js·算法
努力学算法的蒟蒻6 小时前
day34(12.15)——leetcode面试经典150
算法·leetcode·面试
星川皆无恙6 小时前
基于ARIMA 算法模型和NLP:社交媒体舆情分析在涉众型经济犯罪情报挖掘中的应用研究
人工智能·爬虫·python·算法·机器学习·自然语言处理·数据分析
椰羊~王小美6 小时前
因重载方法使用不当导致报错
开发语言
zore_c6 小时前
【C语言】Win 32 API——一部分内容详解!!!
c语言·开发语言·c++·经验分享·笔记
郑州光合科技余经理6 小时前
定制开发实战:海外版外卖系统PHP全栈解决方案
java·服务器·开发语言·javascript·git·uni-app·php
郝学胜-神的一滴6 小时前
Linux线程编程:从原理到实践
linux·服务器·开发语言·c++·程序人生·设计模式·软件工程
Chen--Xing6 小时前
LeetCode 11.盛最多水的容器
c++·python·算法·leetcode·rust·双指针
良木生香6 小时前
【数据结构-初阶】详解线性表(3)---双链表
c语言·数据结构·蓝桥杯