C 练习实例36 – 求100之内的素数(长文讲解)
💡一则或许对你有用的小广告
欢迎加入小哈的星球 ,你将获得:专属的项目实战(已更新的所有项目都能学习) / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论
- 新开坑项目:《Spring AI 项目实战》 正在持续爆肝中,基于 Spring AI + Spring Boot 3.x + JDK 21..., 点击查看 ;
- 《从零手撸:仿小红书(微服务架构)》 已完结,基于
Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...
,点击查看项目介绍 ;演示链接: http://116.62.199.48:7070 ;- 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/ ;
截止目前, 星球 内专栏累计输出 90w+ 字,讲解图 3441+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 3100+ 小伙伴加入学习 ,欢迎点击围观
在编程学习的旅程中,练习实例是巩固理论知识的重要环节。C练习实例36 – 求100之内的素数 是一个经典且实用的编程任务,它不仅涉及数学逻辑的思考,还要求开发者掌握循环、条件判断等基础语法。对于编程初学者而言,这道题目能帮助理解算法设计的逻辑性;而对中级开发者来说,它也是一个优化代码效率的绝佳案例。本文将从素数的基本概念出发,逐步拆解问题,提供清晰的代码实现,并深入探讨如何通过优化算法提升性能。
一、素数的定义与数学基础
1.1 素数是什么?
素数(Prime Number)是指大于1的自然数中,除了1和它本身以外,无法被其他自然数整除的数。例如,2、3、5、7 是素数,而4(能被2整除)、9(能被3整除)则不是。我们可以将素数比喻为“数字世界中的原子”,因为它们无法被更小的自然数分解。
1.2 为什么需要求素数?
素数在密码学、数据安全等领域有广泛应用,例如RSA加密算法的核心就是基于大素数的分解难题。而本题的目标是“求100以内的素数”,虽然数值范围较小,但其核心逻辑可以拓展到更大的范围。
二、问题拆解与算法设计
2.1 问题分析
我们需要找出所有小于或等于100的素数。直观的思路是:
- 遍历2到100之间的每一个数。
- 对每个数判断是否为素数。
- 将符合条件的数记录下来。
2.2 如何判断一个数是否为素数?
对于一个数n
,若存在除1和它本身之外的因数,则它不是素数。因此,判断素数的核心逻辑是:
- 从2开始,遍历到
n-1
,检查是否存在能整除n
的数。 - 若存在,则
n
不是素数;否则,是素数。
但这样的方法效率较低,可以通过数学优化减少计算量。
三、基础代码实现
3.1 基础版代码
以下是直接按照“遍历到n-1
”的思路编写的代码:
#include <stdio.h>
int is_prime(int n) {
if (n <= 1) return 0; // 小于等于1的数不是素数
for (int i = 2; i < n; i++) {
if (n % i == 0) {
return 0; // 存在因数,不是素数
}
}
return 1; // 是素数
}
int main() {
printf("100以内的素数有:\n");
for (int num = 2; num <= 100; num++) {
if (is_prime(num)) {
printf("%d ", num);
}
}
return 0;
}
代码解释
is_prime
函数接收一个整数n
,返回1(是素数)或0(非素数)。- 当
n
小于等于1时,直接返回0(如1、0、负数)。 - 循环从2到
n-1
,若发现能整除n
的数,立即返回0。 - 若循环结束未找到因数,则返回1。
输出结果
运行此代码将输出:
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
四、算法优化与性能提升
4.1 优化点1:遍历到平方根
数学规律表明:如果n
有一个大于其平方根的因数,那么必然存在一个小于平方根的对应因数。例如,对于n=25
,因数5的平方根是5,因此只需检查到5即可。
修改后的is_prime
函数如下:
int is_prime(int n) {
if (n <= 1) return 0;
int sqrt_n = (int)sqrt(n); // 计算平方根
for (int i = 2; i <= sqrt_n; i++) {
if (n % i == 0) {
return 0;
}
}
return 1;
}
优化效果
当n=100
时,原方法需要遍历到99,而优化后仅需遍历到10,计算量减少了近90%!
4.2 优化点2:跳过偶数
除了2以外,所有偶数都不是素数。因此,在遍历因数时,可以只检查奇数:
int is_prime(int n) {
if (n <= 1) return 0;
if (n == 2) return 1; // 2是唯一的偶数素数
if (n % 2 == 0) return 0; // 排除其他偶数
int sqrt_n = (int)sqrt(n);
for (int i = 3; i <= sqrt_n; i += 2) { // 步长改为2,只检查奇数
if (n % i == 0) {
return 0;
}
}
return 1;
}
综合优化效果
结合上述两点,算法的效率将显著提升。例如,当判断n=99
时,原方法需要检查到98次,而优化后仅需检查到9次(平方根为9.949,取整后为9)。
五、进阶案例:生成素数表
5.1 使用数组记录结果
除了逐个输出,也可以将素数存储在数组中,便于后续处理。
#include <stdio.h>
#include <math.h>
#define MAX 100
int main() {
int primes[MAX + 1] = {0}; // 初始化为0,素数标记为1
for (int num = 2; num <= MAX; num++) {
if (primes[num] == 0) { // 未被标记为非素数
// 标记所有倍数为非素数
for (int multiple = num * num; multiple <= MAX; multiple += num) {
primes[multiple] = 1;
}
}
}
printf("100以内的素数:\n");
for (int i = 2; i <= MAX; i++) {
if (primes[i] == 0) {
printf("%d ", i);
}
}
return 0;
}
算法原理
- 这是“埃拉托斯特尼筛法”(Sieve of Eratosthenes)的简化版本。
- 初始化一个数组,初始值全为0。
- 从2开始,若当前数未被标记(即为素数),则标记其所有倍数为非素数。
- 最终,未被标记的数即为素数。
优势
此方法的时间复杂度为O(n log log n),比逐个判断更高效,尤其适用于较大的范围(如求10000以内的素数)。
六、常见错误与调试技巧
6.1 错误1:遗漏边界条件
- 问题:未处理
n=2
的情况,导致2被错误标记为非素数。 - 解决:在判断偶数时,单独处理
n=2
。
6.2 错误2:平方根计算不准确
- 问题:使用
sqrt()
函数时未包含头文件<math.h>
,或未强制类型转换。 - 解决:确保代码开头有
#include <math.h>
,并用(int)sqrt(n)
获取整数部分。
6.3 调试技巧
- 逐步打印:在关键位置添加
printf
语句,观察中间变量的值。 - 单元测试:单独测试
is_prime
函数,例如:printf("is_prime(7) = %d\n", is_prime(7)); // 应输出1 printf("is_prime(9) = %d\n", is_prime(9)); // 应输出0
七、扩展思考与应用场景
7.1 扩展问题
- 问题1:如何求1000以内的素数?
- 答案:只需修改代码中的
MAX
值为1000,并确保内存足够。
- 答案:只需修改代码中的
- 问题2:如何统计某个范围内的素数数量?
- 答案:在循环中增加计数器变量,例如:
int count = 0; for (int num = 2; num <= 100; num++) { if (is_prime(num)) count++; } printf("共有%d个素数\n", count);
- 答案:在循环中增加计数器变量,例如:
7.2 实际应用场景
- 密码学:RSA算法依赖大素数的乘积,其因数分解难度构成加密基础。
- 随机数生成:素数常用于构建伪随机数生成器的参数。
- 数据分块:在分布式计算中,素数可作为分块策略的优化参数。
结论
通过C练习实例36 – 求100之内的素数,我们不仅掌握了素数的基本判断方法,还学会了如何通过数学优化和算法设计提升代码效率。从基础的遍历法到高效的筛法,这一过程体现了编程中“先解决问题,再优化方案”的核心思想。对于开发者而言,理解素数的特性并灵活运用算法优化,是应对更复杂问题的关键。
建议读者动手实现不同版本的代码,并尝试扩展到更大的数值范围。通过实践,你将更深刻地理解算法的底层逻辑,并在编程之路上迈出坚实的一步。