质数
质数又称素数。质数定义为大于 1 的自然数中,除了 1 和它本身以外不再有其他约数的数称为质数。除了 1 和质数以外的自然数称为合数。 1 既不是质数也不是合数。所以质数又被称为不可约数。
质数与合数的性质
- a>1 是合数,当且仅当 a=bc,其中 1< b < a,1 < c < a
- 合数必有质数因子
- 如果 d > 1, p 是质数,且 d∣p,则 d=p
- 设 p 是质数且 p∣ab,则必有 p∣a 或者 p∣b
- 存在无穷多个质数
判断质数
关于怎么判断一个数 n 是否是质数,最简单的方法是枚举 2 到 n - 1,判断是否是 n 的约数。如果是, n 肯定不是一个质数。再仔细想想,如果 a 是 n 的一个约数,那么必然有一个 b 满足 ab=n, 和 中必然有一个成立,因为如果 并且 ,那么 ab>n,和 ab=n 矛盾。因此如果 n 是一个合数,那么我们只需要枚举到 就一定能找到 n 的一个约数。否则,n 肯定是一个质数。
int is_prime(int n) {
for (int i = 2; i * i <= n; ++i) {
if (n % i == 0) {
return 0; // 不是质数
}
}
return 1; // 是质数
}
Eratosthenes 筛选
更多的时候,需要预处理出一段区间上的质数,如果按照之前的方法一个一个判断,时间上肯定会承受不了。于是提出了一种预处理 1 到 N 上质数的算法,称为 Eratosthenes 筛选。
素数筛选算法的基本思想是我们先假设 2 到 N 上所有数都是素数。我们从 2 开始扫描,对于一个数 i,可以得到 2i,3i⋯ki 都不是素数,因为至少这些数都有 i 这个因子,表示这些数不是素数,一直枚举到 N。对于每一个合数,它至少会被它的一个因子枚举到,所以可能证明这个算法的正确性。接下来分析时间复杂度,对于每个 i,枚举的次数为 ,所以总得时间复杂度为
for (int i = 2; i <= n; ++i) {
is_prime[i] = 1;
}
for (int i = 2; i <= n; ++i) {
for (int j = i * 2; j <= n; j += i) {
is_prime[j] = 0;
}
}
优化
上面的代码便筛选出来了 n 以内的素数,如果 is_prime[i] = 1,i 是素数,否则 i 是合数。上面的代码还可以优化。第一是基于每个合数必然有一个质因子,所以我们可以只用质数来筛选,第二是 j 的初始条件可以写成 ,因为比如 j=i*k(k<i),那么 j 肯定被 k 筛选掉了。第三是可以只用 之前的质数去筛选。优化之后的时间复杂度比 还要低得多。优化以后的代码如下。
for (int i = 2; i <= n; ++i) {
is_prime[i] = 1;
}
for (int i = 2; i * i <= n; ++i) {
if (is_prime[i]) {
for (int j = i * i; j <= n; j +=i) {
is_prime[j] = 0;
}
}
}