当前位置: 首页 > news >正文

【C语言】统计孪生素数对高效解法

使用埃拉托斯特尼筛法(Sieve of Eratosthenes) 的高效解法,时间复杂度为 O (n log log n),显著优于逐个判断素数的 O (n²) 复杂度:

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>int count_prime_pairs(int n) {if (n < 5) return 0; // 小于5时无孪生素数对// 创建布尔数组标记素数,初始全为true(假设都是素数)bool* is_prime = (bool*)calloc(n + 1, sizeof(bool));if (!is_prime) return -1; // 内存分配失败// 初始化:0和1不是素数,其余默认是素数for (int i = 2; i <= n; i++) is_prime[i] = true;// 埃拉托斯特尼筛法:标记非素数for (int i = 2; i * i <= n; i++) {if (is_prime[i]) {for (int j = i * i; j <= n; j += i) {is_prime[j] = false;}}}// 统计孪生素数对int count = 0;for (int p = 3; p <= n - 2; p += 2) { // 从3开始只检查奇数if (is_prime[p] && is_prime[p + 2]) {count++;}}free(is_prime); // 释放内存return count;
}int main() {int n = 10;printf("孪生素数对数量:%d\n", count_prime_pairs(n)); // 输出:2return 0;
}

核心优化点

  1. 筛法求素数

    • 一次性标记所有非素数,避免重复判断。
    • 时间复杂度 O (n log log n),接近线性时间。
  2. 内存优化

    • 使用布尔数组替代整数数组,节省空间。
    • 动态分配内存适应不同的 n 值。
  3. 奇数优化

    • 孪生素数对中,p 和 q 必须都是奇数(除了 (3,5) 外,其他偶数不可能是素数)。
    • 循环步长设为 2,跳过偶数检查。
  4. 边界处理

    • 当 n < 5 时直接返回 0,减少不必要计算。

代码核心理解

1. bool* is_prime = (bool*)calloc(n + 1, sizeof(bool)); 的语法解释

这行代码使用了 C 语言的动态内存分配函数 calloc,其作用是在程序运行时动态分配一块连续的内存空间,并初始化为 0。具体解析如下:

  • calloc 函数原型void* calloc(size_t num_elements, size_t element_size);

    • num_elements:需要分配的元素数量(这里是 n + 1,表示从 0 到 n 的所有索引)
    • element_size:每个元素的大小(这里是 sizeof(bool),通常为 1 字节)
    • 返回值:指向分配内存的指针(类型为 void*,需要强制转换为目标类型)
  • 强制类型转换 (bool*):将 void* 类型的指针转换为 bool* 类型,以便赋值给 is_prime 变量。

  • 内存初始化calloc 会自动将分配的内存全部初始化为 0(即每个 bool 值都为 false),这与 malloc 不同(malloc 分配的内存值是未定义的)。

2. 埃拉托斯特尼筛法(Sieve of Eratosthenes)的逻辑

埃拉托斯特尼筛法是一种高效的素数筛选算法,其核心思想是:从 2 开始,将每个素数的倍数标记为合数。具体步骤如下:

// 初始化:0和1不是素数,其余默认是素数
for (int i = 2; i <= n; i++) is_prime[i] = true;// 埃拉托斯特尼筛法:标记非素数
for (int i = 2; i * i <= n; i++) {if (is_prime[i]) {for (int j = i * i; j <= n; j += i) {is_prime[j] = false;}}
}

  • 初始化阶段:创建一个布尔数组 is_prime,将所有索引(2 到 n)初始化为 true,表示默认都是素数。

  • 筛选阶段

    1. 外层循环:从 2 开始遍历到 √n(因为如果一个数 n 不是素数,它一定有一个因子小于等于 √n)。
    2. 检查当前数 i:如果 is_prime[i] 仍为 true,说明 i 是素数(未被前面的步骤标记为合数)。
    3. 标记合数:将 i 的所有倍数(从 i*i 开始,因为更小的倍数已经被之前的素数标记过)标记为 false
  • 优化点

    • 从 i*i 开始:例如,当 i=5 时,直接从 25 开始标记,因为 5*2=105*3=155*4=20 已经被 2 和 3 标记过。
    • 只遍历到 √n:超过 √n 的数的倍数已经被更小的素数标记过。

3. free(is_prime); 的作用:为什么要释放内存?

在 C 语言中,使用 malloccalloc 或 realloc 动态分配的内存必须在不再使用时手动释放,否则会导致内存泄漏(Memory Leak)。具体原因如下:

  • 内存生命周期:动态分配的内存位于堆(Heap)区,不会像栈(Stack)上的局部变量那样在函数返回时自动释放。如果不手动释放,这块内存会一直被占用,直到程序结束。

  • 内存泄漏后果:反复分配内存而不释放,会导致程序占用的内存不断增长,最终耗尽系统资源,导致程序崩溃或系统变慢。

  • 正确做法

    1. 使用 calloc 分配内存。
    2. 使用完内存后(通常在不再需要该内存的地方,如函数结束前),调用 free 释放它。
    3. 将指针置为 NULL(可选),防止误操作:is_prime = NULL;
  • 类比:就像借书需要归还图书馆一样,动态分配的内存也需要归还给系统,以便其他程序或本程序的其他部分可以使用。

相关文章:

  • vsCode使用本地低版本node启动配置文件
  • 1panel面板中部署SpringBoot和Vue前后端分离系统 【图文教程】
  • 解析奥地利 XARION激光超声检测系统:无膜光学麦克风 + 无耦合剂的技术协同优势及多元应用
  • 氛围编程时代来临:本地化智能分析破解老项目 AI 编程困局
  • 【Go语言基础【6】】字符串格式化说明
  • 保姆级【快数学会Android端“动画“】+ 实现补间动画和逐帧动画!!!
  • MySQL体系架构解析(二):MySQL目录与启动配置全解析
  • 命令行以TLS/SSL显式加密方式访问FTP服务器
  • for AC500 PLCs 3ADR025003M9903的安全说明
  • 【Algo】常见组合类数列
  • dify官方离线安装打包插件脚本,改造成ubuntu或者debain系统可用
  • 使用vtk8.2.0加载dicom图像
  • 离散傅里叶级数(DFS)的用途
  • 深入了解JavaScript当中如何确定值的类型
  • 几何引擎对比:OpenCasCade、ACIS、Parasolid和CGM
  • 讲解:Java I/O 流体系,并举例每个类的使用
  • 一站式万能邮箱:如何实现多平台邮件管理?
  • 使用绑定参数以防止SQL注入攻击
  • SpringBoot自动配置原理深度解析
  • Flutter:下拉框选择
  • 红塔网站制作/推广软件排行榜前十名
  • wordpress和织梦架构/神马搜索seo优化排名
  • 湘西州住房和城乡建设局网站/济南网站建设方案
  • 洋桥网站建设公司/网站权重是怎么提升的
  • 电商网络营销是干什么的/免费seo公司
  • 汕头seo优化/免费seo排名优化