第三十五天:寻找质数
寻找质数
一、质数的定义与特性
质数(又称素数)是指大于1的自然数中,除了1和其本身外,不能被其他自然数整除的数。具体特性如下:
-
基本定义:
- 必须是大于1的正整数
- 仅有两个正因数(1和自身)
- 数量无限(欧几里得已证明)
-
主要性质:
- 最小质数为2,且是唯一的偶质数
- 质数序列:2,3,5,7,11,13,17,19,23,29…
- 任何大于1的整数要么是质数,要么可分解为质数的乘积
-
应用领域:
- 密码学:RSA加密算法利用大质数的难以分解特性
- 计算机科学:哈希表常选用质数大小以降低冲突概率
- 数学研究:与哥德巴赫猜想、黎曼假设等重大问题相关
-
判定方法:
- 试除法:用不超过√n的质数逐一试除
- 费马检验:基于费马小定理的概率性检测
- AKS算法:首个多项式时间确定性检测算法
-
特殊类别:
- 梅森素数:形式为2^p-1的质数
- 孪生质数:相差2的质数对(如11与13)
- 安全质数:满足2q+1形式的质数(q同为质数)
二、问题及思路
- 如果一个正整数只能被 1 和它本身整除,就称为一个质数。最小的几个质数依次是 2,3,5,7,11,13,…2,3,5,7,11,13,….请问,第 2025 个质数是多少?
- 输出格式输出一个整数,表示第 2025个质数。
- 要找到第 2025 个质数,最直接的方法就是从最小的质数 2 开始,逐个检查每个整数是否为质数,同时记录找到的质数个数,当找到第 2025 个质数时,输出该数即可。
三、C++ 代码实现
#include <iostream>
#include <cmath>// 判断一个数是否为质数
bool isPrime(int num) {if (num <= 1) return false;if (num == 2) return true;if (num % 2 == 0) return false;for (int i = 3; i <= sqrt(num); i += 2) {if (num % i == 0) return false;}return true;
}int main() {int count = 0; // 记录找到的质数个数int num = 2; // 从最小的质数2开始检查while (true) {if (isPrime(num)) {count++;if (count == 2025) {std::cout << num << std::endl;break;}}num++;}return 0;
}
四、代码解析
- isPrime函数:用于判断一个数是否为质数。
- 首先处理一些特殊情况:如果数字小于等于1,那它肯定不是质数,直接返回false。
- 如果数字是2,2是质数,返回true。
- 如果数字是大于2的偶数,由于偶数能被2整除,所以不是质数,返回false。
- 对于大于2的奇数,从3开始,每次增加2,一直到该数的平方根为止进行检查。这是因为如果一个数不是质数,那么它一定可以分解成两个因数,其中一个因数必定小于等于它的平方根。如果在这个范围内找到了能整除该数的因数,那么这个数就不是质数,返回false;如果遍历完都没有找到,说明这个数是质数,返回true。
- main函数:
- 初始化两个变量,
count
用于记录找到的质数个数,初始值为0;num
从最小的质数2开始,作为被检查的数字。 - 在
while (true)
无限循环中,对num
调用isPrime
函数判断是否为质数。如果是质数,count
加1。当count
达到2025时,说明找到了第2025个质数,输出该数并使用break
跳出循环。每次循环结束后,num
自增1,继续检查下一个数。
- 初始化两个变量,
五、寻找大序号的质数
1、暴力法的局限性
第2025个质数”的暴力思路:从2开始逐个判断是否为质数,直到累计到目标序号。代码逻辑为:
bool isPrime(int n) { /* 暴力判断质数 */ }
int findNthPrime(int n) {int count = 0, num = 2;while (true) {if (isPrime(num)) count++;if (count == n) return num;num++;}
}
问题:当n
很大时(如n=100000
),每个数的质数判断都要遍历到√num
,时间复杂度接近 O(nn)O(n\sqrt{n})O(nn),速度极慢。
2、优化方向:减少重复计算
(1)利用质数分布特性
质数除了2和3,都可表示为6k±1
的形式(证明:6的倍数周围,6k、6k+2、6k+3、6k+4都能被2或3整除,只有6k±1可能是质数)。
优化isPrime
函数:
bool isPrime(int num) {if (num <= 3) return num > 1;if (num % 2 == 0 || num % 3 == 0) return false;for (int i = 5; i * i <= num; i += 6) {if (num % i == 0 || num % (i + 2) == 0) return false;}return true;
}
效果:将质数判断的循环次数减少约1/3(只需检查6k±1
形式的数)。
(2) 预存已知质数,加速判断
如果我们维护一个已找到的质数列表,判断新数是否为质数时,只需用列表中小于等于√num
的质数试除。
示例代码:
#include <vector>
bool isPrime(int num, const std::vector<int>& primes) {for (int p : primes) {if (p * p > num) break; // 超过√num,无需继续if (num % p == 0) return false;}return true;
}int findNthPrime(int n) {std::vector<int> primes = {2, 3}; // 预存小质数if (n <= 2) return primes[n-1];int count = 2, num = 5;while (count < n) {if (isPrime(num, primes)) {primes.push_back(num);count++;}num += 2; // 只检查奇数}return primes.back();
}
原理:因为所有合数都能分解为质数的乘积,所以用已找到的质数试除即可判断新数是否为质数,避免了“从2开始遍历所有数”的冗余。
3、筛法:批量生成质数
当需要找连续区间内的所有质数(从而间接得到大序号质数)时,筛法的效率远超暴力枚举。
(1) 埃拉托斯特尼筛法(Sieve of Eratosthenes)
核心思想:从2开始,把每个质数的倍数标记为合数,剩下的未被标记的就是质数。
示例(找≤N的所有质数):
#include <vector>
std::vector<int> sieve(int n) {std::vector<bool> isPrime(n + 1, true);isPrime[0] = isPrime[1] = false;for (int i = 2; i * i <= n; i++) {if (isPrime[i]) {for (int j = i * i; j <= n; j += i) {isPrime[j] = false;}}}// 收集所有质数std::vector<int> primes;for (int i = 2; i <= n; i++) {if (isPrime[i]) primes.push_back(i);}return primes;
}
局限性:需要预先知道“要找的质数不超过N”,但大序号质数的N难以预估。
(2) 线性筛法(欧拉筛)
优势:保证每个合数仅被其最小质因数标记一次,时间复杂度为 O(n)O(n)O(n),是理论上“筛质数”的最优复杂度。
示例代码:
#include <vector>
std::vector<int> linearSieve(int n) {std::vector<bool> isPrime(n + 1, true);std::vector<int> primes;isPrime[0] = isPrime[1] = false;for (int i = 2; i <= n; i++) {if (isPrime[i]) {primes.push_back(i);}for (int p : primes) {if (i * p > n) break;isPrime[i * p] = false;if (i % p == 0) break; // 保证仅被最小质因数筛除}}return primes;
}
适用场景:需要批量生成连续区间内的质数时,线性筛法是首选。
4、大序号质数的工程化思路
若要找极大序号的质数(如第10^6个),需结合“筛法预生成+局部暴力判断”:
- 步骤1:用线性筛法预生成前
k
个质数(k
为较大的预估值)。 - 步骤2:若预生成的质数数量不足目标序号,从筛法的最后一个数开始,用“预存质数试除”的方法继续找,直到累计到目标序号。
示例逻辑:
int findHugeNthPrime(int n) {// 步骤1:线性筛预生成一批质数int estimate = n * log(n) + n * log(log(n)); // 质数定理估算上界std::vector<int> primes = linearSieve(estimate);// 步骤2:若预生成不足,继续暴力找if (primes.size() >= n) return primes[n-1];int count = primes.size();int num = primes.back() + 2; // 从下一个奇数开始while (count < n) {if (isPrime(num, primes)) {primes.push_back(num);count++;}num += 2;}return primes.back();
}