C语言实现素数判断:算法优化与代码详解

9 次阅读

素数是大于 1 且只能被 1 和自身整除的自然数,例如 2、3、5、7、11、13 等。判断一个整数是否为素数,是 C 语言入门阶段非常典型的算法题,也涉及循环、条件判断、取模运算以及算法复杂度等基础知识。

一、素数判断的基本思路

判断整数 n 是否为素数,本质上是检查它是否存在除 1 和自身之外的其他因数。

最直接的方法是让 i 从 2 开始,一直遍历到 n - 1:

C
#include 

int main(void)
{
    int n;
    int isPrime = 1;

    printf("请输入一个整数:");
    scanf("%d", &n);

    if (n < 2) {
        isPrime = 0;
    } else {
        for (int i = 2; i < n; i++) {
            if (n % i == 0) {
                isPrime = 0;
                break;
            }
        }
    }

    if (isPrime) {
        printf("%d 是素数
", n);
    } else {
        printf("%d 不是素数
", n);
    }

    return 0;
}

这里最关键的运算是:

C
n % i

% 是 C 语言中的取模运算符。如果 n % i == 0,说明 n 可以被 i 整除,因此 n 存在除 1 和自身之外的因数,就可以确定它不是素数。

例如判断 15:

15 % 2 != 0
15 % 3 == 0

发现 15 能被 3 整除后,就没有继续检查的必要,因此使用 break 提前结束循环。

二、为什么不能把 1 判断为素数

素数的定义要求一个数必须大于 1,并且恰好拥有两个正因数:1 和它本身。

因此:

1:只有因数 1,不是素数
2:因数为 1、2,是素数
3:因数为 1、3,是素数
4:因数为 1、2、4,不是素数

代码中通常需要优先处理:

C
if (n < 2) {
    isPrime = 0;
}

这样可以避免将 0、1 以及负数错误地判定为素数。

三、将循环范围优化到 n / 2

实际上,没有必要检查 2 到 n - 1 的所有数字。

如果 n 是合数,并且存在因数 i 满足:

n = i × j

那么至少有一个因数不会超过 n / 2。因此可以将循环条件改成:

C
for (int i = 2; i <= n / 2; i++)

完整代码如下:

C
#include 

int main(void)
{
    int n;
    int isPrime = 1;

    scanf("%d", &n);

    if (n < 2) {
        isPrime = 0;
    } else {
        for (int i = 2; i <= n / 2; i++) {
            if (n % i == 0) {
                isPrime = 0;
                break;
            }
        }
    }

    if (isPrime) {
        printf("%d 是素数
", n);
    } else {
        printf("%d 不是素数
", n);
    }

    return 0;
}

这种写法比遍历到 n - 1 更高效,但还可以继续优化。

四、为什么只需要判断到平方根

素数判断中非常重要的一个优化是:只检查 sqrt(n) 以内的整数。

假设一个数 n 不是素数,那么它可以表示为:

n = a × b

如果 a 和 b 都大于 sqrt(n),那么:

a × b > sqrt(n) × sqrt(n)
     > n

这与 a × b = n 矛盾。

因此,只要 n 存在非 1 和自身的因数,就一定可以在 sqrt(n) 以内找到一个因数。

例如判断 100:

100 = 2 × 50
100 = 4 × 25
100 = 5 × 20
100 = 10 × 10

只检查到 10 就足够了。

五、使用平方根函数实现

可以使用 中的 sqrt():

C
#include 
#include 

int main(void)
{
    int n;
    int isPrime = 1;

    scanf("%d", &n);

    if (n < 2) {
        isPrime = 0;
    } else {
        for (int i = 2; i <= sqrt(n); i++) {
            if (n % i == 0) {
                isPrime = 0;
                break;
            }
        }
    }

    if (isPrime) {
        printf("%d 是素数
", n);
    } else {
        printf("%d 不是素数
", n);
    }

    return 0;
}

这种实现思路非常直观,不过在循环条件中反复调用 sqrt(n) 并没有必要。对于大量判断操作,更推荐避免重复计算。

六、使用 i * i <= n 进一步优化

更常见的写法是:

C
for (int i = 2; i * i <= n; i++)

完整实现:

C
#include 

int main(void)
{
    int n;
    int isPrime = 1;

    scanf("%d", &n);

    if (n < 2) {
        isPrime = 0;
    } else {
        for (int i = 2; i * i <= n; i++) {
            if (n % i == 0) {
                isPrime = 0;
                break;
            }
        }
    }

    if (isPrime) {
        printf("%d 是素数
", n);
    } else {
        printf("%d 不是素数
", n);
    }

    return 0;
}

与 sqrt() 方案相比,这种代码不需要引入 ,同时避免了浮点数运算。

不过,对于可能非常大的整数类型,需要注意 i * i 本身可能发生整数溢出。此时可以写成:

C
for (int i = 2; i <= n / i; i++)

这样可以避免计算 i * i 时超过整数类型的表示范围。

七、跳过偶数进一步减少判断次数

除了 2 之外,所有偶数都不是素数。因此可以单独处理 2,然后只检查奇数:

C
#include 

int isPrime(int n)
{
    if (n < 2) {
        return 0;
    }

    if (n == 2) {
        return 1;
    }

    if (n % 2 == 0) {
        return 0;
    }

    for (int i = 3; i <= n / i; i += 2) {
        if (n % i == 0) {
            return 0;
        }
    }

    return 1;
}

int main(void)
{
    int n;

    printf("请输入一个整数:");
    scanf("%d", &n);

    if (isPrime(n)) {
        printf("%d 是素数
", n);
    } else {
        printf("%d 不是素数
", n);
    }

    return 0;
}

这里的核心优化有三层:

1. n < 2 直接排除
2. 2 单独处理
3. 后续只检查奇数,并且只检查到平方根

这种方式已经足以应对绝大多数 C 语言素数判断场景。

八、为什么推荐封装成 isPrime 函数

将素数判断封装成独立函数,可以让程序结构更加清晰:

C
int isPrime(int n)

函数返回 1 表示是素数,返回 0 表示不是素数。

这样除了判断单个数字,还可以非常方便地应用到其他程序中,例如输出某个范围内的所有素数:

C
#include 

int isPrime(int n)
{
    if (n < 2) {
        return 0;
    }

    if (n == 2) {
        return 1;
    }

    if (n % 2 == 0) {
        return 0;
    }

    for (int i = 3; i <= n / i; i += 2) {
        if (n % i == 0) {
            return 0;
        }
    }

    return 1;
}

int main(void)
{
    for (int i = 1; i <= 100; i++) {
        if (isPrime(i)) {
            printf("%d ", i);
        }
    }

    return 0;
}

运行后可以得到:

2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97

九、不同素数判断算法的效率比较

假设待判断的数字为 n,常见实现方式可以进行如下比较:

判断方式大致判断范围时间复杂度特点
遍历到 n-12 ~ n-1O(n)最基础,效率较低
遍历到 n/22 ~ n/2O(n)减少一部分循环
遍历到平方根2 ~ √nO(√n)推荐使用
平方根 + 跳过偶数奇数至 √nO(√n)实际判断次数更少

对于单次素数判断,O(√n) 已经是非常实用的方案。

例如:

n = 1,000,000
√n = 1,000

原始算法最坏情况下可能需要进行接近百万次除法运算,而平方根算法最多只需要检查约 1000 个候选因数。跳过偶数之后,实际检查次数还会进一步减少。

十、判断大量数字时的算法选择

如果程序只判断一个整数是否为素数,使用试除法即可。

如果需要判断大量连续整数,例如求:

1 到 1000000

范围内的所有素数,逐个调用 isPrime() 虽然可以实现,但效率并不是最高。

这种情况下,可以考虑埃拉托斯特尼筛法。

基本思路是先假设所有大于 1 的数字都是素数,然后从最小素数 2 开始,把它的倍数全部标记为合数。接着找到下一个没有被标记的数字,继续执行。

示例:

C
#include 

#define MAX 100

int main(void)
{
    int prime[MAX + 1];

    for (int i = 0; i <= MAX; i++) {
        prime[i] = 1;
    }

    prime[0] = prime[1] = 0;

    for (int i = 2; i <= MAX / i; i++) {
        if (prime[i]) {
            for (int j = i * i; j <= MAX; j += i) {
                prime[j] = 0;
            }
        }
    }

    for (int i = 2; i <= MAX; i++) {
        if (prime[i]) {
            printf("%d ", i);
        }
    }

    return 0;
}

筛法适合一次性生成一个范围内的全部素数,其核心复杂度约为:

O(n log log n)

当数据范围较大且需要大量查询时,筛法通常比逐个进行试除判断更加合适。

十一、C语言实现素数判断时的常见错误

错误一:把 1 当作素数

例如直接写:

C
int isPrime = 1;

然后没有处理 n < 2 的情况,就可能错误地将 1 判定为素数。

应增加:

C
if (n < 2) {
    return 0;
}

错误二:循环从 1 开始

如果写成:

C
for (int i = 1; i <= n; i++)

那么任何整数都可以被 1 整除,这个判断没有实际意义。

判断素数时应该从:

C
i = 2

开始。

错误三:找到因数后仍然继续循环

一旦发现:

C
n % i == 0

就已经确定 n 不是素数,没有继续计算的必要:

C
return 0;

或者:

C
break;

都可以减少不必要的运算。

错误四:忽略负数和 0

素数必须大于 1,因此:

-10、-3、0、1

都不是素数。

错误五:平方根判断出现整数溢出

对于普通的 int 数据,i * i <= n 通常足够使用,但处理接近类型上限的数据时,应考虑:

C
i <= n / i

这种写法能够降低乘法溢出的风险。

十二、推荐的实用实现

综合代码可读性、执行效率和安全性,一个适合日常使用的版本如下:

C
#include 

int isPrime(int n)
{
    if (n < 2) {
        return 0;
    }

    if (n == 2) {
        return 1;
    }

    if (n % 2 == 0) {
        return 0;
    }

    for (int i = 3; i <= n / i; i += 2) {
        if (n % i == 0) {
            return 0;
        }
    }

    return 1;
}

int main(void)
{
    int n;

    printf("请输入一个整数:");

    if (scanf("%d", &n) != 1) {
        printf("输入无效
");
        return 1;
    }

    printf("%d %s素数
", n, isPrime(n) ? "是" : "不是");

    return 0;
}

这份代码把素数判断独立成 isPrime() 函数,同时利用提前返回、排除偶数、平方根范围等方法降低计算量,适合作为 C 语言学习和实际项目中的基础实现。

十三、算法优化的核心原则

C语言素数判断看似简单,但其中包含了几个非常典型的算法优化思想。

第一是缩小搜索范围。从 n - 1 缩小到 √n,可以显著减少循环次数。

第二是排除不可能的情况。除了 2 之外的偶数不可能成为素数,因此可以直接跳过。

第三是尽早结束。一旦发现能够整除 n 的因数,就立即返回结果。

第四是根据任务规模选择算法。单个数字判断适合试除法;连续范围内寻找大量素数,则可以考虑埃拉托斯特尼筛法。

掌握这些思路后,C语言中的素数判断就不再只是简单的循环练习,而可以进一步理解时间复杂度、边界条件、整数溢出以及算法优化之间的关系。