质数筛选

质数

质数又称素数。质数定义为大于 1 的自然数中,除了 1 和它本身以外不再有其他约数的数称为质数。除了 1 和质数以外的自然数称为合数。 1 既不是质数也不是合数。所以质数又被称为不可约数。

质数与合数的性质

  1. a>1 是合数,当且仅当 a=bc,其中 1< b < a,1 < c < a
  2. 合数必有质数因子
  3. 如果 d > 1, p 是质数,且 d∣p,则 d=p
  4. 设 p 是质数且 p∣ab,则必有 p∣a 或者 p∣b
  5. 存在无穷多个质数

判断质数

关于怎么判断一个数 n 是否是质数,最简单的方法是枚举 2 到 n - 1,判断是否是 n 的约数。如果是, n 肯定不是一个质数。再仔细想想,如果 a 是 n 的一个约数,那么必然有一个 b 满足 ab=n, a n a \le \sqrt{n} b n b \le \sqrt{n} 中必然有一个成立,因为如果 a &gt; n a &gt; \sqrt{n} 并且 b &gt; n b &gt; \sqrt{n} ,那么 ab>n,和 ab=n 矛盾。因此如果 n 是一个合数,那么我们只需要枚举到 n \sqrt{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,枚举的次数为 n i \frac{n}{i} ,所以总得时间复杂度为 N 2 + N 3 + + N N = O ( N lg N ) \frac{N}{2} + \frac{N}{3} + \cdots + \frac{N}{N}=\mathcal{O}(N\lg N)

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 i j=i*i ,因为比如 j=i*k(k<i),那么 j 肯定被 k 筛选掉了。第三是可以只用 n \sqrt{n} 之前的质数去筛选。优化之后的时间复杂度比 O ( N lg N ) \mathcal{O}(N\lg N) 还要低得多。优化以后的代码如下。

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;
        }
    }
}

猜你喜欢

转载自blog.csdn.net/liyuanyue2017/article/details/86654585