素数计算是编程入门阶段的经典练习题,但很多初学者编写的版本存在效率低下的问题。我们先从最基础的素数定义说起:素数是指大于1的自然数,除了1和它本身外没有其他因数。这意味着判断一个数n是否为素数,最直观的方法就是检查2到n-1之间是否存在能整除n的数。
但这种方法在计算100-200区间时效率尚可接受,一旦范围扩大到数万甚至更大,性能问题就会凸显。因此我们需要引入两个关键优化:
在实际编程中,我们使用标志变量法(flag variable)来记录当前数字是否为素数。这种方法直观易懂,适合初学者掌握素数判断的基本逻辑。
让我们仔细分析这个优化后的C语言实现:
c复制#include <stdio.h>
#include <math.h>
int main() {
int i = 0;
for (i = 101; i <= 200; i = i + 2) {
int j = 0;
int flag = 1;
for (j = 2; j <= sqrt(i); j++) {
if (i % j == 0) {
flag = 0;
break;
}
}
if (flag == 1) {
printf("%d ", i);
}
}
return 0;
}
外层循环从101开始,到200结束,步长为2。这种设计直接跳过了所有偶数,因为除了2以外的偶数都不可能是素数。这是第一个关键优化点,将需要检查的数字数量减半。
内层循环从2开始,到当前数字的平方根结束。这里使用了math.h中的sqrt()函数来计算平方根。对于每个待检查的数字i,我们:
注意:使用sqrt()函数时需要链接数学库。在gcc编译时加上-lm参数,如:gcc prime.c -o prime -lm
程序使用printf("%d ", i)打印素数,数字之间用空格分隔。这种输出方式简单直接,适合大多数情况。如果需要更复杂的输出格式(如每行固定数量),可以添加计数器变量来控制。
原始算法(检查2到n-1)的时间复杂度是O(n)。经过平方根优化后,时间复杂度降为O(√n)。对于大数判断,这种优化带来的性能提升非常显著。
举例说明:
虽然当前版本已经不错,但仍有优化空间:
不过对于100-200这样的小范围,当前优化已经足够,更复杂的算法反而会增加代码复杂度。
很多初学者会遇到这样的编译错误:
code复制undefined reference to 'sqrt'
这是因为没有链接数学库。解决方法:
bash复制gcc prime.c -o prime -lm
-lm参数告诉编译器链接数学库。
测试程序时,特别要注意边界条件:
我们可以编写一个未优化的版本来对比性能差异:
c复制// 未优化版本
for(i=100;i<=200;i++) {
int flag = 1;
for(j=2;j<i;j++) {
if(i%j==0) {
flag=0;
break;
}
}
if(flag) printf("%d ",i);
}
在我的测试环境中(i5-8250U,Linux):
将素数判断逻辑提取为独立函数,提高代码复用性:
c复制#include <stdio.h>
#include <math.h>
#include <stdbool.h>
bool isPrime(int n) {
if(n <= 1) return false;
if(n == 2) return true;
if(n % 2 == 0) return false;
for(int i=3; i<=sqrt(n); i+=2) {
if(n % i == 0)
return false;
}
return true;
}
int main() {
for(int i=100; i<=200; i++) {
if(isPrime(i))
printf("%d ", i);
}
return 0;
}
根据关键词要求,这里提供一个Java实现:
java复制public class PrimeNumbers {
public static boolean isPrime(int n) {
if (n <= 1) return false;
if (n == 2) return true;
if (n % 2 == 0) return false;
for (int i = 3; i <= Math.sqrt(n); i += 2) {
if (n % i == 0)
return false;
}
return true;
}
public static void main(String[] args) {
for (int i = 100; i <= 200; i++) {
if (isPrime(i))
System.out.print(i + " ");
}
}
}
Java版本使用了类似的优化逻辑,主要区别在于语法和Math.sqrt()的使用方式。
有时我们需要将结果保存到文件。C语言实现如下:
c复制#include <stdio.h>
#include <math.h>
int main() {
FILE *fp = fopen("primes.txt", "w");
if(fp == NULL) {
printf("Error opening file!\n");
return 1;
}
for(int i=101; i<=200; i+=2) {
int flag = 1;
for(int j=2; j<=sqrt(i); j++) {
if(i%j == 0) {
flag = 0;
break;
}
}
if(flag)
fprintf(fp, "%d ", i);
}
fclose(fp);
return 0;
}
为什么只需要检查到√n就够了?这基于以下数学原理:
假设n是合数,那么n=a×b,其中a和b都大于1。如果a和b都大于√n,那么a×b>n,这与n=a×b矛盾。因此至少有一个因数小于等于√n。
在100-200区间共有21个素数,密度约为21/101≈20.8%。随着数字增大,素数密度逐渐降低,这就是为什么优化算法变得越来越重要。
对于大规模素数计算,还有更高效的算法:
但在小范围内(如100-200),我们的优化版本已经足够高效且实现简单。
素数计算虽然看似简单,但在实际中有重要应用:
对于学习者来说,这个练习的价值在于:
我建议学习者在掌握这个基础版本后,可以尝试: