算法学习笔记(33)——质数
质数
一、试除法判定质数
质数的定义:若一个正整数无法被除了1和它自身之外的任何自然数整除,则称该数为质数(或素数),否则称该正整数为合数。
整个自然数集合中,质数的数量不多,分布比较稀疏,对于一个足够大的整数N,不超过N的质数大约有个,即每个数中大约有一个质数。
试除法:
若一个正整数为合数,则存在一个能整除的数,其中。
时间复杂度:
#include <iostream>
using namespace std;
bool is_prime(int n)
{
// 特判0和1这两个数,他们既不是质数,也不是合数
if (n < 2) return false;
// 从2开始依次试除,此处不使用i * i <= sqrt(n)是为了避免乘积溢出
for (int i = 2; i <= n / i; i ++ ) {
// 若该数能够被除了1和它自身之外的数整除,则其为合数
if (n % i == 0)
return false;
}
// 遍历结束后则证明该数只能被1和它自身整除,则其为质数
return true;
}
int main()
{
int n;
cin >> n;
while (n -- ) {
int a;
cin >> a;
if (is_prime(a)) puts("Yes");
else puts("No");
}
return 0;
}
二、分解质因数
算数基本定理:
任何一个大于 的正整数都能唯一分解为有限个质数的乘积,可写作:
其中 都是正整数, 都是质数,且满足 。
结合质数判定的"试除法",扫描 的每个数 ,如果 是质数,则除掉 里面所有的质因子 。又由于 中最多只含有一个大于 的因子,所以最后判断 是否大于 ,若是则 即为质因子。
时间复杂度:
#include <iostream>
using namespace std;
void divide(int n)
{
for (int i = 2; i <= n / i; i ++ )
// 若i为质因数,则要将该因子除干净
if (n % i == 0) {
int s = 0; // s记录该质因子的次数
while (n % i == 0) {
n /= i;
s ++;
}
cout << i << ' ' << s << endl;
}
// 最多只有1个大于sqrt(n)的质因子
if (n > 1) cout << n << ' ' << 1 << endl;
puts("");
}
int main()
{
int n;
cin >> n;
while (n -- ) {
int a;
cin >> a;
divide(a);
}
return 0;
}
三、筛质数
给定一个整数 ,求出 之间的所有质数,称为质数的筛选问题。
3.1 朴素筛法
任意整数 的倍数 , , ...都不是质数,根据质数的定义,上述规则显然成立。则枚举 的每个数,如果是质数,则将该范围内它的倍数都标记为合数。
时间复杂度:
每一重循环分别会执行 ,,...次,则
又因为调和级数:
其中 为欧拉-马歇罗尼常数,约等于0.5772(无限不循环小数), 约等于 ,并随着 趋于正无穷而趋于 。又因为对数函数真值不变时,底数越大,函数值越小,图像越靠近x轴,所以:
所以我们可以将时间复杂度记作
void get_primes(int n)
{
for (int i = 2; i <= n; i ++ ) {
// 判断是否为质数,若是质数则存起来
if (!st[i]) primes[cnt ++] = i;
// 不管i是质数还是合数,都将其倍数标记为合数
for (int j = i; j <= n; j += i) st[j] = true;
}
}
3.2 埃氏筛法(Eratosthenes 筛法)
分析朴素筛法的过程易知,在遍历2和3时,都会把6标记为合数,因此可以对其进行优化,利用所有的质数就可以标记筛除所有的合数。而对于一个足够大的整数N,不超过N的质数大约有个,则大致估计时间复杂度由朴素筛法的 变为 ,接近线性。
实际的时间复杂度:
void get_primes(int n)
{
for (int i = 2; i <= n; i ++ )
if (!st[i]) {
primes[cnt ++] = i;
// 利用质数筛除值为其倍数的合数
for (int j = i + i; j <= n; j += i) st[j] = true;
}
}
3.3 欧拉筛法(线性筛法)
优化后的埃氏筛法依然会重复标记合数,例如: 既会被 又会被 标记,,。线性筛法通过“从大到小累积质因子”的方式标记每一个合数,即让 仅有 这一种产生方式。使得每个合数只会被他的最小质因子筛一次,时间复杂度为 。
void get_primes(int n)
{
// 依次考虑2~N的每一个数i
for (int i = 2; i <= n; i ++ ) {
// 若为质数,则存起来
if (!st[i]) primes[cnt ++] = i;
// 扫描不大于 n/i 的每个质数(因为primes[j]*i>n时 筛除大于n的合数没有意义)
for (int j = 0; primes[j] <= n / i; j ++ ) {
// 利用最小质因子筛除合数
st[primes[j] * i] = true;
/*
* 如果 i % primes[j] != 0
* 代表 i 的最小质因子还没有找到,即 i 的最小质因子应该大于 primes[j]
* 则 primes[j] 就应该是 primes[j] * i 的最小质因子
* 所以 primes[j] * i 被 primes[j] 筛除
*
* 如果 i % primes[j] == 0
* 代表 i 的最小质因子就是 primes[j]
* 则后续的 primes[j+k] * i 应该被 primes[j] 筛除
* 所以跳出循环,在之后的循环轮次(i更大时)筛除
*/
if (i % primes[j] == 0) break;
}
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效