判断一个数是否是质数。
方法1.
在大于 1 的自然数中,如果 num 有除了 1 和自身以外的因数,说明 num 不是质数,返回 0。
最简单的方法是 i 从 2 到 num-1 都试一遍,看是否能整除 num。
- 若 i 能整除 num,则 num 不是质数,返回 0.
- 若循环结束,没找到能整除 num 的 i,则 num 是质数,返回 1.
设 num 为合数,有 num = i * j。若 i > √num,推出 j 的取值范围。
i = num/j > √num
num > √num * j
等式两边同时除以 √num:√num > j
整理得:j < √num
一个因数 i > √num,则另一个因数 j < √num。在 [2, √num] 范围内,必有一整数能整除 num。换言之,如果遍历到 i > √num 时,未能有 i 能整除 num,则 num 为质数。
所以 i 没必要从 2 到 num-1,从 2 到 √num 就行。
例 53,√53 ≈ 7.28,当循环到 i = 8 时,假设 53 为合数,53 = 8 * j,8 > √53,所以 j < √53,j <= 7,而在之前的遍历中 i = 2、i = 3、… i = 7 并没有出现 num % i == 0 的情况,否则循环会直接终止,所以没有这样的 j 能整除 num。
i <= √num
等式两边平方得:i * i <= num
考虑到 i * i 容易溢出,转为:i <= num/i
int isPrime(int num) {
if (num <= 1) {
return 0;
}
for (int i = 2; i <= num / i; i++) {
// 若 i 能整除 num,则 i 是 num 的因数,所以 num 不是质数,循环终止
if (num % i == 0) {
return 0;
}
}
return 1;
}
方法1、2、3 计算超过 10 亿量级的质数个数有些困难,前者是因为效率低,后者是空间不足。
方法2.
创建数组 primeArr,下标为 num。
- primeArr[num] 为 0,代表 num 是质数。
- primeArr[num] 为 1,代表 num 是合数。
- primeArr[num] 为 -1,代表 num 既不是合数也不是质数。
所有的合数可以分解为多个质数的乘积,设 num 为质数,所有的合数便可以使用 num * k 表示,primeArr[num * k] = 1,k 从 2 到 (length-1)/num。
但请看:
2:4, 6, 8, 10...
3:6, 9, 12, 15...
5:10, 15, 20, 25...
第一轮循环 primeArr[2] = 0、primeArr[4] = 1、primeArr[6] = 1…
第二轮循环 primeArr[3] = 0、primeArr[6] = 1、primeArr[9] = 1…
primeArr[6] 重复设置。
primeArr[3 * 2] 已在 primeArr[2 * 3] 时被设置为 1。
同样的 primeArr[4 * 2] 已在 primeArr[2 * 4] 时被设置为 1;primeArr[4 * 3] 已在 primeArr[3 * 4] 时设置为 1…
综上,k ∈ [2, num-1] 已经在之前的循环中被设置,不妨令 k 从 num 开始。
2:4, 6, 8, 10...
3:9, 12, 15, 18...
5:25, 30, 35, 40...
#include <stdio.h>
#define MAX_LENG 100000
static int primeArr[MAX_LENG];
static int isInit = 0;
int primeInit() {
int primeCount = 0;
primeArr[0] = primeArr[1] = -1;
for (int num = 2; num < MAX_LENG; num++) {
if (primeArr[num] == 0) {
primeCount++;
// 怕 k * num 溢出
for (int k = num; k < (MAX_LENG) / (num+0.0); k++) {
primeArr[num * k] = 1;
}
// 或者使用 i += num,i 为 num 的倍数
/*
for (int i = num * num; i < MAX_LENG; i += num) {
primeArr[i] = 1;
}
*/
}
}
isInit = 1;
return primeCount;
}
int isPrime(int num) {
if (num >= MAX_LENG) {
printf("只支持 [0, %d] 范围内的查询\n", MAX_LENG-1);
return 0;
}
if (!isInit) {
int primeCount = primeInit();
printf("[0, %d] 范围内的质数个数:%d\n", MAX_LENG-1, primeCount);
}
return primeArr[num] == 0 ? 1 : 0;
}
方法3.
方法 2 中也有很多数被重复设置,如
- 12:2 * 6、3 * 4 重复设置。
- 18:2 * 9、3 * 6 重复设置。
- 24:2 * 12、3 * 8 重复设置。
- 45:5 * 9、3 * 15 重复设置。
- …
所以考虑换个方案。
让 k 从 2 到 num。k >= num+1 的部分交给下几轮循环,如 num = 4,k = 5 可以交给 num = 5,k = 4。
2:4
3:6,9
4:8,12,16
5:10,15,20,25
6:12,18,24,30,26
7:14,21,28,35,42,49
8:16,24,32,40,48,56,64
9:18,27,36,45,54,63,72,81
注意看,第 3 行、第 5 行的 12 重复。
num % k == 0
设 num / k = n
那么 num * (k+1) = n*k * (k+1)
num * (k+1) 在之后的循环 num = n*(k+1) 时重复设置
类似的 num * (k+2) = n*(k+2) * k 重复设置
这意味当 k 能整除 num 时,k 没必要++,num * (k+?) 会在之后的循环中被设置,本次循环应到此终止。
例 num = 9,k = 3 时,9 % 3 == 0,9 * 4 = 3 * 3 * 4 = 12 * 3,36 被 9 * 4 和 12 * 3 重复设置。
例 当 num 为偶数,k = 2 时,num % k == 0,则 k 到此终止,没必要++。如 num = 4,k = 2,k = 3、k = 4… 留给 num = 6、num = 8… 设置。
2:4
3:6,9
4:8
5:10,15,20,25
6:12
7:14,21,28,35,42,49
8:16
9:18,27
10:20
11:22,33,44,55,66,77,88,99,110,121
12:24
13:26,39,52,65,78,91,104,117,130,143,156,169
注意,第 10 行的 44 = 11 * 4 = 11 * 2 * 2 = 22 * 2。
由于合数 4 可以分解为多个质数的乘积,所以在 num = 22,k = 2 时,也重复设置了。需要规定 k 必须为质数,即 k = 2、3、5、7、11、13、17…
2:4
3:6,9
4:8
5:10,15,25
6:12
7:14,21,35,49
8:16
9:18,27
10:20
11:22,33,55,77,121
12:24
13:26,39,65,91,143,169
#include <stdio.h>
#define MAX_LENG 1000000
static int primeArr[MAX_LENG];
static int isInit = 0;
int primeInit() {
primeArr[0] = primeArr[1] = -1;
int primeCount = 0;
for (int num = 2; num < MAX_LENG; num++) {
if (primeArr[num] == 0) {
primeCount++;
}
// k ∈ [2, num]
// 第2个条件可以修改为 k < (MAX_LENG) / (num+0.0)
for (int k = 2; k <= num && k*num < MAX_LENG; k++) {
// k 必须为质数
if (primeArr[k] == 0) {
primeArr[num * k] = 1;
// 若 num % k == 0,设 num = n * k
// num *(k+1)、num *(k+2)... 留给 num = n*(k+1)、n*(k+2)...时设置
if (num % k == 0) break;
}
}
}
isInit = 1;
return primeCount;
}
int isPrime(int num) {
if (num >= MAX_LENG) {
printf("只支持 [0, %d] 范围内的查询\n", MAX_LENG-1);
return 0;
}
if (!isInit) {
int primeCount = primeInit();
printf("[0, %d] 范围内的质数个数:%d\n", MAX_LENG-1, primeCount);
}
return primeArr[num] == 0 ? 1 : 0;
}
10以内的质数个数:4
100以内的质数个数:25
1000以内的质数个数:168
10000以内的质数个数:1229
100000以内的质数个数:9592
1000000以内的质数个数:78498
10000000以内的质数个数:664579
100000000以内的质数个数:5761455
1000000000以内的质数个数:50847534