最近更新于 2024-05-05 14:19
素数定义
在大于1的自然数中,除了1和它本身以外不再有其他因数的自然数
代码测试环境
gcc 9.4.0 – 64位
编译参数:
-no-pie -std=c17 -Wall -Werror=return-type -Werror=address -Werror=sequence-point -Werror=format-security -Wextra -pedantic -Wimplicit-fallthrough -Wsequence-point -Wswitch-unreachable -Wswitch-enum -Wstringop-truncation -Wbool-compare -Wtautological-compare -Wfloat-equal -Wshadow=global -Wpointer-arith -Wpointer-compare -Wcast-align -Wcast-qual -Wwrite-strings -Wdangling-else -Wlogical-op -Wconversion -g -O0 -lm
方法一
判断某个数 N 能不能被 [2,N) 之间所有整数整除,当然也可以先行筛选掉一些明显特征的。
根据素数的特征分析出条件如下:
(1)2 是最小的素数
(2)大于 2 的素数必然不是偶数
(3)实际并不需要将 N 依次除以 [2,N) 之间的所有数,只需要除以 [2,√N] 之间的整数判断。
这个原理该怎么理解呢?
一个数 N = m × n,当 m = n 时,m=n=√N。若减小 m,那么 n 就增大,若增大 m,那么 n 就会减小,始终有 m × n = N。
如 12 ÷ 2 = 6,也有 12 ÷ 6 = 2,√12≈3.4.64。在已知 12 可以被 2 整除的情况下,对于大于 √N = 3.464 的 6 也就不需要再判断了,凡是大于 √N 的因数必然有一个小于 √N 的因数与之相乘等于 N。
#include <math.h>
#include <stdio.h>
#include <stdbool.h>
#define N 100 // 筛选素数的范围 [2,100]
int main()
{
for (int i = 2; i <= N; ++i)
{
if (i != 2 && i % 2 == 0) // 跳过偶数
{
continue;
}
bool is_prime = true;
int mid_factor = (int)sqrt(i); // 取中间因数
for (int j = 2; j <= mid_factor; ++j)
{
if (i % j == 0)
{
is_prime = false;
break;
}
}
if (is_prime == true)
{
printf("%d ", i);
}
}
printf("\n");
}

测试筛选一千万以内的素数(如果没有 printf 打印显示,速度会相当快,为了方便比较算法效率,保留 printf 可以放大时间)

方法二:埃氏筛选

在前面我写的方法一基础上再加条件,前面是 N 依次除以 [2, √N] 之间所有整数,现在改为除以 [2,√N] 之间所有素数,进一步缩小计算量。
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <math.h>
#define N 10000000 // 筛选素数的范围
//////////////////////////////////////////////////
// 简单动态数组实现
#define INIT_LEN 10 // 初始数组容量
#define ADD_LEN 50 // 容量不够时,每次扩容量
typedef struct Array
{
int *data; // 存放数据
size_t size; // 数据实际长度
size_t capacity; // 容量
} Array;
/**
* @brief 创建一个动态数组对象
*
* @return Array* 返回创建的对象
*/
Array *create_array()
{
Array *new_array = malloc(sizeof(Array));
int *new_data = malloc(sizeof(int) * INIT_LEN);
new_array->data = new_data;
new_array->size = 0;
new_array->capacity = INIT_LEN;
return new_array;
}
/**
* @brief 向数组中添加元素
*
* @param array 要添加元素的数组对象
* @param val 要添加的值
*/
void add(Array *array, int val)
{
if (array->size + 1 > array->capacity) //扩容
{
array->data = realloc(array->data, sizeof(int) * (array->capacity + ADD_LEN));
array->capacity += ADD_LEN;
}
array->data[array->size] = val;
++array->size;
}
/**
* @brief 下标索引数组元素值
*
* @param array 要索引的数组对象
* @param idx 下标值
* @return int 索引结果
*/
int get(Array *array, size_t idx)
{
if (idx >= array->size)
{
return -1;
}
return array->data[idx];
}
/**
* @brief 返回数组长度
*
* @param array 要获取长度的数组对象
* @return size_t 数组长度
*/
size_t len(Array *array)
{
return array->size;
}
/**
* @brief 释放数组(结束时必须调用该函数释放数组占用的内存)
*
* @param array 要释放的数组对象
*/
void free_array(Array *array)
{
free(array->data);
array->data = NULL;
free(array);
array = NULL;
}
// 简单动态数组实现
//////////////////////////////////////////////////
int main()
{
Array *prime = create_array(); // 用于存放素数
add(prime, 2);
for (int i = 3; i <= N; i = i + 2) // 遍历筛选范围内的数 - 偶数直接排除
{
bool is_prime = true;
int mid_factor = (int)sqrt(i); // 计算边界 √N,N 除以所有 [2,√N] 之间的素数
size_t length = len(prime); // 已有的素数个数
for (size_t j = 0; j < length; ++j)
{
int div = get(prime, j);
if (div > mid_factor)
{
break;
}
if (i % div == 0)
{
is_prime = false;
break;
}
}
if (is_prime == true)
{
add(prime, i);
}
}
for (size_t i = 0; i < len(prime); ++i)
{
printf("%d ", get(prime, i));
}
printf("\n");
free_array(prime);
}
下面就是直接测试筛选一千万以内的素数,耗用的时间接近前一种方法的一半

素数筛选
