埃拉托斯特尼筛法是一种古老而高效的素数筛选算法,其核心思想是通过逐步排除合数来找出所有素数。这个算法由古希腊数学家埃拉托斯特尼在公元前3世纪提出,至今仍然是寻找小素数最高效的方法之一。
算法的执行过程可以形象地比喻为"筛沙子":
这个算法的高效之处在于它只需要进行到√n即可,因为任何大于√n的合数必定已经被更小的素数筛除过。
让我们深入分析提供的C语言实现代码:
c复制#include<stdio.h>
int prime[100] = {0};
void init_prime(int n) {
prime[0] = prime[1] = 1;
for (int i = 2; i * i <= n; i++) {
if (prime[i]) continue;
printf("%d is prime : ", i);
for (int j = i * i; j <= n; j += i) {
prime[j] = 1;
printf(" %d", j);
}
printf("\n");
}
return ;
}
int main() {
init_prime(50);
int x;
while (scanf("%d", &x) != EOF) {
printf("prime[%d] = %d\n", x, prime[x]);
}
return 0;
}
这段代码有几个关键点值得注意:
数组标记法:使用一个全局数组prime[100]来标记数字是否为合数。数组初始化为0,表示所有数字最初都被认为是素数。当数字被确定为合数时,对应位置被标记为1。
优化起点:内层循环从i*i开始,而不是从2*i开始。这是因为更小的倍数已经被之前的素数筛除过了。例如,当处理素数5时,5×2=10已经被2筛除,5×3=15已经被3筛除,5×4=20已经被2筛除,所以直接从5×5=25开始。
终止条件:外层循环的条件是i*i <= n,这相当于i <= √n,是算法效率的关键。
当前实现使用int类型数组来存储标记,实际上每个标记只需要1位。可以考虑以下优化:
unsigned char数组,每个元素存储8个标记位malloc动态分配内存c复制// 位操作优化示例
#define GET_BIT(arr, n) (arr[n/8] & (1 << (n%8)))
#define SET_BIT(arr, n) (arr[n/8] |= (1 << (n%8)))
unsigned char prime_bits[(MAX_N+7)/8] = {0};
c复制// 跳过偶数优化示例
void init_prime_optimized(int n) {
prime[0] = prime[1] = 1;
// 处理偶数
for (int j = 4; j <= n; j += 2) {
prime[j] = 1;
}
// 只处理奇数
for (int i = 3; i * i <= n; i += 2) {
if (prime[i]) continue;
for (int j = i * i; j <= n; j += 2*i) {
prime[j] = 1;
}
}
}
c复制while (scanf("%d", &x) != EOF) {
if (x < 0 || x >= 100) {
printf("Input out of range (0-99)\n");
continue;
}
printf("prime[%d] = %d\n", x, prime[x]);
}
埃拉托斯特尼筛法不仅用于判断素数,还可以用于:
埃拉托斯特尼筛法的时间复杂度为O(n log log n),空间复杂度为O(n)。这比逐个检查每个数是否为素数的O(n√n)方法要高效得多。
i*i <= n而非i <= n可以通过以下方式测试算法性能:
clock()函数测量执行时间c复制#include <time.h>
int main() {
clock_t start = clock();
init_prime(1000000);
clock_t end = clock();
printf("Time: %.2f seconds\n", (double)(end - start) / CLOCKS_PER_SEC);
return 0;
}
这段代码作为教学示例具有很高的价值,它展示了:
对于学习者,建议:
通过这样的实践,可以深入理解算法思想并提升编程能力。