sqrt(n) 是判断上限的关键,因为若n有大于sqrt(n)的因子d,则必存在对应小于sqrt(n)的因子n/d,故只需试除到sqrt(n)即可严格判定素数。

为什么 sqrt(n) 是判断上限的关键
素数定义是大于1且只能被1和自身整除的正整数。暴力试除时,若 n 有大于 sqrt(n) 的因子 d,那必然存在对应的小于 sqrt(n) 的因子 n/d。所以只需检查到 sqrt(n) 即可——不是“大概可以”,而是数学上严格等价。
常见错误是写成 i 放在循环条件里:每次迭代都调用 sqrt(浮点运算+类型转换),还可能因精度问题漏判或越界。正确做法是提前算一次整数上界:int limit = static_cast,或更稳妥地用 i * i 避开浮点。
i * i 更快、无精度风险,但注意i别溢出(n接近INT_MAX时,i较大时i*i可能溢出;此时改用long long i或切回sqrt)- 对
n == 2要单独返回true,否则i=2时i*i 不成立,直接跳过循环误判为合数 - 偶数只需检查
n == 2,其余偶数直接返回false,省一半时间
is_prime 函数怎么处理边界和小数字
实际代码里最容易翻车的是 n 、n == 2、n == 3 这几个点。C++ 没有内置素数判断,必须手动覆盖:
n → 直接false(0、1、负数都不是素数)-
n == 2→true(唯一偶素数) -
n % 2 == 0→false(排除其余所有偶数) -
n == 3→ 会被后续奇数循环捕获,但提前写if (n == 3) return true;并不必要,反而冗余
示例精简写法:
立即学习“C++免费学习笔记(深入)”;
bool is_prime(int n) {
if (n < 2) return false;
if (n == 2) return true;
if (n % 2 == 0) return false;
for (int i = 3; i * i <= n; i += 2) {
if (n % i == 0) return false;
}
return true;
}需要批量判断时,别手写循环——用埃氏筛 sieve_of_eratosthenes
如果要判断 [2, N] 区间内所有数是否为素数(比如 N=1e6),单个 is_prime 调用 N 次是 O(N√N),而埃氏筛是 O(N log log N),快一个数量级以上。
核心逻辑是:从 2 开始,把每个素数的倍数全标为合数。关键细节:
- 数组大小至少为
N+1,索引即数值(is_prime[i]表示 i 是否为素数) - 外层循环只需到
sqrt(N),因为大于 √N 的素数,其最小未标记倍数必 > N - 内层标记从
i * i开始,不是2*i——因为更小的倍数(如2*i,3*i…)已被更小的素因子筛过了 - 用
vector节省内存,但注意它特化实现可能慢;高频访问场景可用vector
6k±1 优化真有必要吗
所有大于3的素数都形如 6k ± 1(因为其他形式:6k, 6k+2, 6k+3, 6k+4 都能被2或3整除)。利用这点可跳过更多合数,但实际收益有限:
- 相比只试奇数(步长2),6k±1 把试除次数降到约 1/3,理论加速 ~1.5×
- 但分支变多、循环逻辑复杂,现代 CPU 分支预测失败成本可能抵消收益
- 对单次判断几乎没意义;仅当
n极大(如 > 1e12)且调用频繁时才值得考虑,此时还要配合 Miller-Rabin - 日常使用坚持
i += 2就够了,清晰、稳定、易调试
真正容易被忽略的是:int 溢出和 unsigned 类型混用。比如用 unsigned int 存 n,再写 i * i ,当 i 接近 65536 时 i*i 会回绕,导致无限循环。统一用 long long i 或加溢出检查更稳妥。










