算术基本定理:每个整数 n > 1 都有唯一的素数分解 n = p₁^a₁ × p₂^a₂ × ... × pₖ^aₖ。
朴素判素法与优化
最直接的判素法:从 2 试除到 n-1。但我们只需要试到 √n——因为如果 n 有因子 d,那么 n/d 也是因子,d 和 n/d 中至少一个 ≤ √n。
package main
import (
"fmt"
"math"
)
// 朴素判素:O(√n)
func isPrime(n int) bool {
if n < 2 { return false }
if n == 2 { return true }
if n%2 == 0 { return false }
sqrtN := int(math.Sqrt(float64(n)))
for i := 3; i <= sqrtN; i += 2 {
if n%i == 0 { return false }
}
return true
}
// 试除法分解质因数
func factorize(n int) map[int]int {
factors := map[int]int{}
for p := 2; p*p <= n; p++ {
for n%p == 0 {
factors[p]++
n /= p
}
}
if n > 1 {
factors[n]++
}
return factors
}
func main() {
fmt.Println(isPrime(17), isPrime(18)) // true false
fmt.Println(factorize(360)) // map[2:3 3:2 5:1] 360=2³×3²×5
}
埃拉托斯特尼筛法:批量生成素数
埃氏筛法的思路:从 2 开始,把每个素数的倍数标记为合数。时间复杂度 O(n log log n),是生成 1 到 n 所有素数的最快实用算法。
package main
import "fmt"
// 埃拉托斯特尼筛法:返回 [2, n] 内的所有素数
func sieve(n int) []int {
isComposite := make([]bool, n+1)
primes := []int{}
for i := 2; i <= n; i++ {
if isComposite[i] {
continue
}
primes = append(primes, i)
// 从 i² 开始标记(i 以下的倍数已被更小的素数标记过)
for j := i * i; j <= n; j += i {
isComposite[j] = true
}
}
return primes
}
func main() {
primes := sieve(50)
fmt.Println(primes)
// [2 3 5 7 11 13 17 19 23 29 31 37 41 43 47]
fmt.Println("50 以内共", len(primes), "个素数")
// 素数计数函数 π(n):估计 ≈ n / ln(n)
// π(50) ≈ 50/ln(50) ≈ 12.8,实际 15,误差合理
}
Miller-Rabin 概率素性测试
对大数(如 RSA 的 1024 位整数),试除法太慢。Miller-Rabin 是概率性测试——选 k 个随机基,如果都通过测试,那么 n 是素数的概率 ≥ 1 - 4^(-k)。实际使用 k=20 次就足够安全。
package main
import (
"fmt"
"math/rand"
)
func powMod(base, exp, mod int64) int64 {
result := int64(1)
base %= mod
for exp > 0 {
if exp&1 == 1 { result = result * base % mod }
exp >>= 1
base = base * base % mod
}
return result
}
// Miller-Rabin 单次测试
func millerTest(d, n int64) bool {
a := 2 + rand.Int63n(n-4)
x := powMod(a, d, n)
if x == 1 || x == n-1 { return true }
for d != n-1 {
x = x * x % n
d *= 2
if x == 1 { return false }
if x == n-1 { return true }
}
return false
}
func isPrimeMR(n int64, k int) bool {
if n < 2 { return false }
if n < 4 { return true }
if n%2 == 0 { return false }
d := n - 1
for d%2 == 0 { d /= 2 }
for i := 0; i < k; i++ {
if !millerTest(d, n) { return false }
}
return true
}
func main() {
fmt.Println(isPrimeMR(1_000_000_007, 20)) // true(大素数)
fmt.Println(isPrimeMR(1_000_000_008, 20)) // false
}
RSA 加密的数论基础
RSA 的安全性依赖大整数分解的困难性:选两个大素数 p 和 q,公布 n = p×q,加密用 n 和公钥 e,解密需要知道 p 和 q——而从 n 分解出 p、q 在数学上没有已知的多项式时间算法。目前最快的分解算法是数域筛法(Number Field Sieve),复杂度是次指数级。
✓推荐做法
- 1 到 n 的素数批量用埃氏筛法
- 单个大数判素用 Miller-Rabin(k≥20)
- 质因数分解用试除法到 √n,余数大于 1 就也是质因子
✗不推荐
- 对大数用朴素试除法判素——1e9 以上就已经很慢了
- 认为「分解质因数」有快算法——这是 RSA 的安全假设
⚠常见误区
- 埃氏筛从 i² 开始标记时,i² 可能溢出 int——用 int64 或先检查
判断标准:能解释 RSA 的数论依据,并手写埃氏筛法 → 掌握本章。
素数是数学中永恒的谜题,也是现代密码学的地基。
— 数论导引