很多学算法的朋友都有这样一种体验:提起素数好像都知道,可只要话题一转,变成“从 1 到一千万里快速筛出全部素数”,就开始在埃氏筛和欧拉筛之间犹豫。我第一次系统认真想欧拉筛,是在准备某个算法专题整理的晚上,被“线性筛为什么真的是 O(n)”这个问题问得头皮发麻——当时能背模板,也写出了正确答案,但每一行为什么这么写,说不利索。这篇内容,想把从素数定义、暴力试除、埃氏筛到欧拉筛的整条认知线理顺。它适合刚接触数论基础的中学生、编程入门者,也适合准备算法笔试、竞赛的读者,因为欧拉筛几乎是常见筛法题里默认要掌握的工具。整篇围绕素数与欧拉筛展开,不光给代码,更想把每一行背后的代价和理由讲清楚。
1. 素数定义里最容易忽略的三个边界
1.1 一字之差,数学性质完全不同
教科书上的素数定义通常是这样:一个大于 1 的自然数,如果除了 1 和它自身以外不再有其他正因数,就称为素数。这句话读起来很短,真正放到代码里却有不少容易出错的地方。
先看自然数范围。素数问题默认只在正整数范围内讨论,负数不是素数,0 也不是素数。很多同学第一次写判断函数时,会写一个 for 循环从 2 试到 n-1,但忘了先处理 n <= 1 的情况。于是输入 1 时返回 true,输入 0 时也返回 true,整个结果马上乱掉。
再看 2 这个特殊情况。2 是唯一一个偶数素数,也是最小的素数。判断一个偶数是否为素数时,直接返回 false 就够了;而判断奇数时,可以用更小的循环范围。这些看起来是琐碎细节,但所有筛法代码的初始化都绕不开它们:比如数组一开始全部标记为 true,就必须手动把 isPrime[0] 和 isPrime[1] 改成 false。
1.2 1 被“开除”出素数队伍的原因
很多人小时候都问过一个问题:1 为什么不是素数?它也确实“只有 1 和它本身”两个概念相符。但在数学上,如果 1 也算素数,整个数论体系会受到很大冲击。
最关键的是唯一分解定理:任何一个大于 1 的自然数,都能唯一地分解成有限个素数的乘积,并且在不计顺序的情况下写法唯一。这句话被称为算术基本定理,是数论的基石之一。如果 1 是素数,那么 6 = 2 × 3 = 1 × 2 × 3 = 1 × 1 × 2 × 3,分解方式就无限多种,“唯一性”立刻失效。为了避免这种
