在某一天起来,脑海中浮现一连串数字分别是2、3、5、7、11、13、17,反应过来好像是一串质数
于是马上写了个基础的质数检测函数
1、试除法
bool is_prime(int num)
{
if(num <= 1) return false;
for(int i=2;i<num;i++)
{
if(num%i == 0)
return false;
}return true;
}
最简单、最基础的质数检测函数,具体实现方式就是让num将2-num-1之间的所有数取余,直到找不到一个能够被模的数,那么num就是质数
但是这种方法一旦需要被检测的数非常大就会发生很艰难的运算,例如以下实例:
让这个方法举例所有200以内的质数,然后再求int类型中最大的质数2147483647,来直观的判断程序运行时间
可以看到,举例200以内的质数用了8ms,而判断int类型中最大的质数居然用了5095ms!这是何等漫长!
于是需要改进算法,优化试除法
2、优化试除法
bool is_prime(int num)
{
if(num<=1) return false;
if(num<=3) return true;
if (num % 2 == 0 || num % 3 == 0) return false;
for(int i = 5;i*i<=num+1;i+=6)
{
if(num%i ==0 || num%(i+2) == 0)
return false;
}
return true;
}
优化试除法是在试除法的基础上进行了优化后的方法,1不是质数,可以直接排除,2、3是质数也可以排除
if (num % 2 == 0 || num % 3 == 0) return false;这里排除了所有2、3的倍数的整数、
为什么 i 要从5开始呢,因为 2 和 3 的倍数已经筛了,那就从5开始了, i+=6 刚好跳过2 和3 的倍数
要求不变,可以看到举例200以内的质数已经达到了6ms,而判断一个大数是否为质数的速度居然缩短了一大截,只有886ms,比优化前快了5倍!
经过这么一折腾,试除法的效率已经提到了最高,继续优化没有意义了,该换方法了
3、埃拉托斯特尼筛法
埃拉托斯特尼筛法简称埃氏筛,是一种基于排除法的筛选质数方法,原理是将已知质数的倍数筛出,剩下的就是质数了
bool is_prime(int num) {
vector<bool> prime(num + 1, true);
prime[0] = prime[1] = false;
for (int p = 2; p * p <= num; p++) {
if (prime[p]) {
for (int i = p * p; i <= num; i += p)
prime[i] = false;
}
}
return prime[num];
}
代码中定义了一个prime数组,初始默认全是质数,为true,然后通过2、3质数的倍数进行筛出合数,再在数组中更改true为false,剩下筛出来的就是质数了,直接返回 prime[num]即可
可以看到,打印200以内的质数用了8ms(跟试除法有得一比),而且大数判断直接寄了,不过本来埃氏筛就是为了求出一定范围内的质数,而太大的质数意味着prime数组需要非常大的空间,直接溢出了很正常
4、欧氏筛
欧氏筛和埃氏筛目的一样,都是为了筛出一定范围内的质数,而不是用来求一个数是否为质数的方法,代码是
bool is_prime(int num)
{
if(num<=1) return false;
vector<bool> status(num+1,true);
int m = int(sqrt(num));
status[0] = status[1] = false;
for(int i = 2;i<=num;i++)
{
if(status[i])
{
if(i == num) return true;
for(int j = i*i ; j<=num;j+=i)
status[j] = false;
}
}
return false;
}
原理同埃氏筛,不过略有不同,是通过筛选质因子来筛除合数,
显而易见的,因为要定义一个数组来存储数字标签,故而在判断大数时一定会寄掉
5、Miller-Rabin质数测试
这是一个很高级的质数检测方法,Miller-Rabin素性测试是一种概率性的素性测试方法,用于判断一个给定的整数是否可能是素数。它基于费马小定理的一个推广,并结合了一些额外的数学原理来提高测试的准确性。
Miller-Rabin测试步骤
-
选择随机基:选择一个随机数a∈{2,3,…,n−2}a∈{2,3,…,n−2}。
-
计算a^d(%n)a^d(%n):计算a^d(%n)a^d(%n)。如果ad≡1(modn)ad≡1(modn)或ad≡−1(modn)ad≡−1(modn),则nn可能是素数。
-
迭代测试:对于r=0,1,…,s−1r=0,1,…,s−1,计算a2rd(modn)a2rd(modn)。如果a2rd≡−1(modn)a2rd≡−1(modn),则停止并认为nn可能是素数。否则,如果a2rd≡1(modn)a2rd≡1(modn)且r<s−1r<s−1,则nn肯定不是素数。
// 快速幂取模函数:计算 (base^exp) % mod
int powMod(int base, int exp, int mod) {
int result = 1; // 初始结果为 1
while (exp > 0) { // 当指数 exp 大于 0 时循环
if (exp % 2 == 1) // 如果 exp 是奇数
result = (result * base) % mod; // 结果乘以当前 base,并取模
exp = exp >> 1; // 将 exp 右移一位,相当于 exp // 2
base = (base * base) % mod; // base 变为 base^2,并取模
}
return result; // 返回计算结果
}
// 模乘函数:计算 (a * b) % mod
int mulMod(int a, int b, int mod) {
return ((long long)a * b) % mod; // 使用 long long 避免溢出
}
// 素性检测函数:使用 Miller-Rabin 算法判断 num 是否为素数
bool is_prime(int num, int k = 5) {
if (num <= 1 || num == 4) return false; // 1 和 4 不是素数
if (num <= 3) return true; // 2 和 3 是素数
int d = num - 1; // 计算 d = num - 1
// 将 d 变成 2 的幂次方乘以奇数
while (d % 2 == 0) d /= 2;
// 初始化随机数生成器
random_device rd; // 获取随机数种子
mt19937 gen(rd()); // 使用随机数种子初始化 Mersenne Twister 随机数生成器
uniform_int_distribution<> dis(2, num - 2); // 生成 2 到 num-2 之间的均匀分布的整数
for (int i = 0; i < k; i++) { // 进行 k 次测试
int a = dis(gen); // 随机选取一个 a
int x = powMod(a, d, num); // 计算 a^d % num
if (x == 1 || x == num - 1) continue; // 如果 x 是 1 或 num - 1,则继续下一个测试
while (d != num - 1) { // 将 d 还原到最初的值
x = mulMod(x, x, num); // 计算 x^2 % num
d *= 2; // d 加倍
if (x == 1) return false; // 如果 x 变成 1,则 num 不是素数
if (x == num - 1) break; // 如果 x 变成 num - 1,则继续下一个测试
}
if (x != num - 1) return false; // 如果 x 最终不等于 num - 1,则 num 不是素数
}
return true; // 经过 k 次测试均未发现非素数,num 被认为是素数
}
可以看到这个方法对于获取一定范围内的数速度很慢,但是在处理大数质数方面非常快,直接秒了,是个很高级的方法,可惜结果不准确,因为是采用了随机种子使得结果不能完全相信