在整个自然数集合中, 质数的数量不多, 分布比较稀疏, 对于一个足够大的整数 N, 不超过 N 的质数大约有 N/lnN 个, 即每 lnN 个数中大约有 1 个质数。
质数的判定
试除法
若一个正整数 N 为合数, 则存在一个能整除 N 的数 T, 其中 2≤T≤N 。
证明: 由定义得, 因为 N 是合数, 所以存在一个能整除 N 的数 M, 其中 2≤M≤N−1。 反证法: 假设命题不成立, 那么这样的数 M 一定满足 N+1≤M≤N−1 。因为 M 能整除 N, 所以它们的商 N/M 也能整除 N 。而 2≤N/M≤N, 令 T=N/M, 这与假设矛盾。故假设不成立, 原命题成立。
根据上述命题, 我们只需要扫描 2∼N 之间的所有整数, 依次检查它们能否整除 N, 若都不能整除, 则 N 是质数, 否则 N 是合数。试除法的时间复杂度为 0(N) 。
当然, 我们需要特判 0 和 1 这两个整数, 它们既不是质数, 也不是合数。
1 2 3 4 5 6 7
boolisPrime(int n){ if (n < 2) returnfalse; for (int i = 2; i <= n / i; ++i) { if (n % i == 0) returnfalse; } returntrue; }
// 线性筛法 int v[MAX_N], prime[MAX_N]; voidprimes(int n){ memset(v, 0, sizeof(v)); // 最小质因子 m = 0; // 质数数量 for (int i = 2; i <= n; i++) { if (v[i] == 0) { // i是质数 v[i] = i; prime[++m] = i; } // 给当前的数i乘上一个质因子 for (int j = 1; j <= m; j++) { // i有比prime[j]更小的质因子,或者超出n的范围 if (prime[j] > v[i] || prime[j] > n/i) break; // prime[j]是合数i*prime[j]的最小质因子 v[i*prime[j]] = prime[j]; } } for (int i = 1; i <= m; i++) cout << prime[i] << endl; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14
voidgetPrimes(int n){ vector<bool> notprime(n + 1); vector<int> prime; for (int i = 2; i <= n; ++i) { if (!notprime[i]) prime.push_back(i); for (auto p : prime) { if (p > n / i) break; notprime[i * p] = true; if (i % p == 0) break; } } for (auto p : prime) cout << p << endl; }
质因数分解
算术基本定理
任何一个大于 1 的正整数都能唯一分解为有限个质数的乘积, 可写作:
N=p1c1p2c2⋯pmcm
其中 ci 都是正整数, pi 都是质数, 且满足 p1<p2<⋯<pm 。
试除法
结合质数判定的 “试除法” 和质数筛选的 “Eratosthenes 筛法”, 我们可以扫描 2∼∣N∣ 的每个数 d, 若 d 能整除 N, 则从 N 中除掉所有的因子 d, 同时累计除去的 d 的个数。
因为一个合数的因子一定在扫描到这个合数之前就从 N 中被除掉了, 所以在上述过程中能整除 N 的一定是质数。最终就得到了质因数分解的结果, 易知时间复杂度为 O(N) 。
特别地, 若 N 没有被任何 2∼N 的数整除, 则 N 是质数, 无需分解。
1 2 3 4 5 6 7 8 9 10 11
voiddivide(int n){ m = 0; for (int i = 2; i <= n / i; ++i) { if (n % i == 0) { // i是质数,且 i 能整除 n p[++m] = i, c[m] = 0; while (n % i == 0) n /= i, c[m]++; // 除掉 n 中所有的 i } } if (n > 1) p[++m] = n, c[m] = 1; // n 是质数 for (int i = 1; i <= m; ++i) cout << p[i] << '^' << c[i] << endl; }