目录
[C 语言实现代码及详细注释](#C 语言实现代码及详细注释)
埃拉托斯特尼筛法(Sieve of Eratosthenes) 是一种非常古老且高效的算法,用于找出一定范围内所有的素数。
算法核心思想
算法的核心思想非常巧妙:从 2 开始,将每个素数 的所有倍数 标记为非素数(合数)。
-
创建列表 :首先创建一个从
2
到n
的连续整数列表。 -
选取素数 :选取第一个未被标记的数
p
(它一定是素数)。当我们找到第一个未被标记的数p
(即sieve[p]
为True
)时,它一定是素数,因为如果它是合数,它必定有小于p
的质因子。但由于我们从 2 开始逐步标记倍数,任何小于p
的质因子都已经处理过,并标记了p
的倍数(包括p
本身)为合数。因此,如果p
未被标记,说明没有小于p
的质因子能标记它,所以p
是素数。 -
标记倍数 :将
p
的所有倍数(2p
,3p
,4p
, ...)标记为合数。 -
重复步骤 :重复步骤 2 和 3,直到
p² > n
。 -
得到结果:所有未被标记的数即为素数。
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
关键点解释
-
动态内存分配:
-
由于
n
可能非常大(如 100 亿),我们不能使用静态数组(如bool sieve[100000000001]
),因为这会在栈上分配内存,导致栈溢出。 -
使用
malloc
在堆上申请内存,堆的空间远大于栈。
-
-
优化:外层循环条件
i * i <= size
:-
这是一个关键优化。如果一個数
size
是合数,那么它一定有一个小于或等于sqrt(size)
的质因数。 -
因此,我们只需要用
[2, sqrt(size)]
范围内的素数去标记它们的倍数,就能确保所有合数都被标记出来。这大大减少了循环次数。
-
-
优化:内层循环从
i * i
开始:-
对于素数
i
,比i*i
小的倍数(如i*2
,i*3
, ...,i*(i-1)
)已经被比i
更小的素数(如2
,3
, ...)标记过了。 -
从
i*i
开始标记避免了大量的重复工作。
-
-
空间与时间的权衡:
- 该算法是一种典型的"以空间换时间"的算法。我们需要一个大小为
n+1
的数组,但换来了O(n log log n)
的时间复杂度,这非常高效。
- 该算法是一种典型的"以空间换时间"的算法。我们需要一个大小为
算法可视化
假设我们要找出 n=20
以内的所有素数,其过程如下所示。图表清晰地展示了筛选的每一步,从2开始,依次标记其倍数,直到所有合数都被标记出来。
希望这个详细的解释能帮助你完全理解 C 语言中的埃拉托斯特尼筛法!