✅作者简介:人工智能专业本科在读,喜欢计算机与编程,写博客记录自己的学习历程。
🍎个人主页:小嗷犬的博客
🍊个人信条:为天地立心,为生民立命,为往圣继绝学,为万世开太平。
🥭本文内容:C/C++中的素数判定
更多内容请见👇
什么是素数
素数又称质数。一个大于
1
的自然数,除了1
和它自身外,不能被其他自然数整除的数叫做素数;否则称为合数(规定1
既不是素数也不是合数)。
在许多的程序设计题目中,都会涉及到素数的判断,那我们该如何有效判断素数呢?
素数的两种判断方法
暴力法
从 2 到 √n
根据素数的定义,我们可以使用逐个试除的方式来判断素数,如果能为要判断的数找到一个除了
1
和自身以外的因数,那么它就是合数;反之,就是素数。
而这样的因数的范围必然在
2 ~ √n
之间,所以我们便可以得到以下代码。
int isPrime(int n)
{
if(n <= 1)
{
return 0;
}
for (int i = 2; i * i <= n; i++)
{
if (n % i == 0)
{
return 0;
}
}
return 1;
}
该函数就可以判断输入的数是否为素数。
这个范围还可以更进一步地缩小。
6n-1与6n+1
数学上有一个定理,除了
2
和3
外,只有形如6n-1
和6n+1
的自然数可能是素数,这里的n
是大于等于1
的整数。
如何理解这个定理呢?
所有自然数都可以写成6n
,6n+1
,6n+2
,6n+3
,6n+4
,6n+5
这6
种。
那么我们就可以得到下表。
自然数 | 是否可能是素数 |
---|---|
6n | 不可能,为2 的倍数 |
6n+1 | 可能 |
6n+2 | 不可能,为2 的倍数 |
6n+3 | 不可能,为3 的倍数 |
6n+4 | 不可能,为2 的倍数 |
6n+5 | 可能 |
其中
6n+5
可以写作6n-1
,所以除了2
和3
的素数必然形如6n-1
或6n+1
。
于是我们可以写出如下代码。
int isPrime(int n)
{
if(n <= 1) return 0;
else if(n == 2 || n == 3) return 1;
else if(n % 6 != 1 && n % 6 != 5) return 0;
for (int i = 5; i * i <= n; i++)
{
if (n % i == 0)
{
return 0;
}
}
return 1;
}
优化后的算法具有更高的效率。
筛法
暴力算法虽然可以判断某个数是否为素数,但是当它面对大量需要判断的数据时,它的效率会显得十分低下,我们也有更好地方法来求一定范围里的素数,它就是我们的筛法。
筛法,顾名思义,就是将合数从数据中筛除,剩下的自然就都是素数了。
筛法也分为两种,让我们来逐一介绍。
埃氏筛
埃拉托斯特尼 筛法,简称 埃氏筛,是一种由希腊数学家埃拉托斯特尼所提出的一种简单检定素数的算法。
要得到自然数 n 以内的全部素数,必须把不大于根号 n 的所有素数的倍数剔除,剩下的就是素数。
下面的程序就是通过埃氏筛判断
2 ~ N
是否为素数。
#define N 1000000
int prime[N];
bool is_prime[N];
int Eratosthenes(int n)
{
int p = 0;
for (int i = 0; i <= n; ++i)
is_prime[i] = 1;
is_prime[0] = is_prime[1] = 0;
for (int i = 2; i <= n; ++i)
{
if (is_prime[i])
{
prime[p++] = i; // prime[p]是i,后置自增运算代表当前素数数量
if ((long long)i * i <= n)
for (int j = i * i; j <= n; j += i)
// 因为从 2 到 i - 1 的倍数我们之前筛过了,这里直接从 i 的倍数开始,提高了运行速度
is_prime[j] = 0; // 是 i 的倍数的均不是素数
}
}
return p;
}
埃氏筛的时间复杂度是
O(n*loglogn)
,效率相较于原来的暴力算法已经有了很大的提高,但它仍然有具有一定的不足。
对于多个素数的公倍数,可能会被多次筛去。
为了解决这个问题,数学家欧拉优化了算法,于是就有了新的筛法。
欧拉筛
欧拉筛法,简称欧拉筛或是欧式筛,又因为其
O(n)
的时间复杂度而被称为线性筛。
欧拉筛将合数分解为(
最小质因数 * 一个合数
)的形式,通过最小质因数来判断当前合数是否已经被标记过,与埃氏筛相比,不会对已经被标记过的合数再进行重复标记,故效率更高。
下面的程序就是通过欧拉筛判断
2 ~ N
是否为素数。
#define N 1000000
int cnt;
int pri[N], vis[N];
void Euler(int n)
{
for (int i = 2; i <= n; ++i)
{
if (!vis[i])
{
pri[cnt++] = i;
}
for (int j = 0; j < cnt; ++j)
{
if (1ll * i * pri[j] > n)
break;
vis[i * pri[j]] = 1;
if (i % pri[j] == 0)
{
// i % pri[j] == 0
// 换言之,i 之前被 pri[j] 筛过了
// 由于 pri 里面质数是从小到大的,所以 i 乘上其他的质数的结果一定会被
// pri[j] 的倍数筛掉,就不需要在这里先筛一次,所以这里直接 break 就好了
break;
}
}
}
}
在求一定范围中的所有素数时,欧拉筛具有无可比拟的优势,在程序设计中也经常被采用。