判断一个数是否是质数

判断一个数是否是质数。

方法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…

24
369
48
5101525
612
714213549
816
91827
1020
1122335577121
1224
1326396591143169
#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
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值