资讯动态

欧拉函数:从定义到RSA加密,四种计算方法与实战应用

发布时间:2026/8/18 4:13:05 来源:尧图企业网站定制
1. 项目概述为什么我们需要深入理解欧拉函数在数论的世界里欧拉函数Euler‘s totient function是一个绕不开的核心概念。我第一次接触它是在解决一个关于模运算和循环节的问题时当时感觉像是拿到了一把打开新世界的钥匙。简单来说对于一个正整数 n欧拉函数 φ(n) 表示的是小于等于 n 的正整数中与 n 互质的数的个数。比如 φ(8) 4因为 1, 3, 5, 7 这四个数与 8 互质。这个概念听起来简单但它背后连接着费马小定理、欧拉定理、RSA加密算法等一系列重量级理论是初等数论向高等数论迈进的关键桥梁。很多朋友在学习数论时往往停留在“知道定义”的层面一旦遇到需要高效计算 φ(n) 的编程题比如求一个很大范围内所有数的欧拉函数值或者需要利用其性质进行数学推导时就感到无从下手。这正是因为对欧拉函数的理解不够“立体”。仅仅记住定义就像只认识汽车的四个轮子却不知道如何驾驶它。我们需要系统地掌握它的基本性质、多种计算方法以及这些方法背后的适用场景和效率考量。无论是准备算法竞赛还是深入密码学、纯数学研究对欧拉函数的透彻理解都是一项基本功。本文将从一个实践者的角度带你从性质到实现彻底吃透欧拉函数让你不仅能“看懂”更能“用好”。2. 欧拉函数的核心性质与数学内涵要灵活运用欧拉函数首先必须深刻理解它的几个核心性质。这些性质不仅是理论推导的基石更是我们设计高效算法的灵感来源。2.1 基本定义与积性函数特性欧拉函数 φ(n) 最根本的定义已经提及。它的第一个关键性质是积性函数Multiplicative Function。这意味着如果两个正整数 a 和 b 互质即 gcd(a, b) 1那么 φ(ab) φ(a) * φ(b)。这个性质极其重要它允许我们将一个复杂的大数 n 的欧拉函数计算分解为对其质因数幂次形式的各个部分分别计算然后再相乘。例如计算 φ(35)。因为 35 5 × 7且 5 和 7 互质所以 φ(35) φ(5) * φ(7)。而 φ(5)41,2,3,4φ(7)61,2,3,4,5,6因此 φ(35)4*624。你可以验证在1到35之间确实有24个数与35互质。注意积性函数的前提是“互质”。如果 a 和 b 不互质这个性质一般不成立。例如φ(4)2φ(6)2但 φ(24)8而 2*24 ≠ 8。2.2 针对质数幂次的计算公式基于积性我们自然需要知道对于一个质数的幂次 p^kp是质数k是正整数φ(p^k) 如何计算。这里有一个非常直观的推导在 1 到 p^k 这 p^k 个数中有多少个数与 p^k 不互质呢只要一个数包含质因子 p它就不与 p^k 互质。而在 1 到 p^k 之间p 的倍数有p, 2p, 3p, ..., p^k。这正好是 p^(k-1) 个数。因此与 p^k 互质的数的个数就是总数减去这些倍数φ(p^k) p^k - p^(k-1) p^k * (1 - 1/p)。这个公式是推导通用公式的基础。例如φ(8) φ(2^3) 2^3 - 2^2 8 - 4 4与我们之前列举的结果一致。2.3 通用计算公式及其推导结合积性函数性质和质数幂次公式我们可以得到欧拉函数的通用计算公式。将任意正整数 n 进行质因数分解n p1^k1 * p2^k2 * ... * pm^km其中 pi 是互不相同的质数。 由于各个 p_i^ki 之间两两互质根据积性 φ(n) φ(p1^k1) * φ(p2^k2) * ... * φ(pm^km) 再将每个 φ(pi^ki) 用公式展开 φ(n) [p1^k1 * (1 - 1/p1)] * [p2^k2 * (1 - 1/p2)] * ... * [pm^km * (1 - 1/pm)] 将所有的 p_i^ki 乘到一起正好就是 n因此φ(n) n * (1 - 1/p1) * (1 - 1/p2) * ... * (1 - 1/pm)这就是最著名的欧拉函数计算公式。它清晰地揭示了 φ(n) 的值只与 n 的质因数种类有关而与其次数无关次数信息已隐含在 n 中。例如对于 n122^2 * 3那么 φ(12) 12 * (1 - 1/2) * (1 - 1/3) 12 * (1/2) * (2/3) 4。你可以验证在1到12中与12互质的数是1, 5, 7, 11正好是4个。3. 实战计算四种方法详解与场景选择理解了理论接下来就是实战。计算 φ(n) 有多种方法各有优劣适用于不同场景。选择正确的方法往往能让效率提升几个数量级。3.1 公式法单点计算的利器当你只需要计算单个或少数几个 n 的 φ(n) 值时公式法通常是首选因为它思路直接代码清晰。其核心步骤就是对 n 进行质因数分解然后套用通用公式。实操步骤初始化结果ans n。从 i2 开始遍历到 sqrt(n)。如果 i 能整除 n说明 i 是 n 的一个质因子在循环中由于我们总是用 n 除以 i 直到除不尽所以每次遇到的 i 必然是质数。当找到一个质因子 i 时执行ans ans / i * (i - 1)。这等价于ans n * (1 - 1/i)但避免了浮点数运算。同时将 n 中的所有 i 因子除尽while (n % i 0) n / i;。循环结束后如果 n 1说明剩下的 n 本身就是一个大于 sqrt(原n) 的质因子需要同样处理ans ans / n * (n - 1)。代码示例C风格int euler_phi_formula(int n) { int ans n; int temp n; // 保留原n的副本用于分解 for (int i 2; i * i temp; i) { if (temp % i 0) { ans ans / i * (i - 1); // 应用公式 while (temp % i 0) temp / i; // 除尽该质因子 } } if (temp 1) { // 处理剩余的大质因子 ans ans / temp * (temp - 1); } return ans; }注意事项循环条件i * i temp是关键优化确保只遍历到 sqrt(temp)。随着 temp 被不断除尽这个上界会动态减小。运算ans ans / i * (i - 1)必须先除法再乘法以防止中间结果溢出。确保ans能被i整除根据公式它一定能整除。这种方法的时间复杂度主要取决于质因数分解的速度最坏情况n是质数为 O(√n)。3.2 递推法基于定义与公约数的朴素求解递推法更贴近定义适合教学理解或对效率要求不高的极小范围计算。其思路是利用欧几里得算法辗转相除法判断每个数是否与 n 互质。实操步骤初始化计数器count 0。遍历 i 从 1 到 n。对每个 i计算 gcd(i, n)。如果 gcd(i, n) 1则计数器加一。遍历结束后计数器的值即为 φ(n)。代码示例int gcd(int a, int b) { return b 0 ? a : gcd(b, a % b); } int euler_phi_naive(int n) { int count 0; for (int i 1; i n; i) { if (gcd(i, n) 1) { count; } } return count; }方法评价与避坑优点实现简单逻辑与定义完全一致不易出错。缺点效率极低时间复杂度为 O(n log n)每次 gcd 计算需要 O(log n)。对于 n 超过 10^5 的情况基本不可用。常见误区初学者可能忘记处理 n1 的情况。根据定义φ(1)1因为1与1互质。上述循环从1到n当n1时i1gcd(1,1)1count1结果是正确的。但有些实现从i2开始遍历就需要单独处理n1。3.3 线性筛法欧拉筛批量计算的王者当需要计算一个较大范围内例如 1 到 NN 在 10^6 到 10^7 量级所有数的欧拉函数值时线性筛法是唯一的选择。它能在 O(N) 的时间复杂度内一次性求出所有 φ(i) (i1..N)。这是算法竞赛和高效编程中的必备技能。核心原理线性筛法在筛选质数的同时利用欧拉函数的积性性质递推地计算出每个数的 φ 值。我们需要维护两个数组is_prime[]标记是否为质数phi[]存储欧拉函数值。递推关系这是算法的精髓对于质数 pφ(p) p - 1。这很好理解因为 1 到 p-1 所有数都与 p 互质。令当前遍历到的质数为 primes[j]当前遍历的数为 i。如果i % primes[j] 0即 primes[j] 是 i 的最小质因子那么i * primes[j]这个数就包含了 primes[j] 的更高次幂。可以推导出φ(i * primes[j]) φ(i) * primes[j]。因为 primes[j] 的因子已经在 i 的因子中所以乘以 primes[j] 只是增加了倍数没有引入新的质因子与 i 互质的数的比例不变只是总数扩大了 primes[j] 倍。如果i % primes[j] ! 0即 primes[j] 不是 i 的因子那么 i 和 primes[j] 互质。根据积性函数性质φ(i * primes[j]) φ(i) * φ(primes[j]) φ(i) * (primes[j] - 1)。实操步骤与代码const int MAXN 1000000; // 根据需要调整范围 int phi[MAXN 5]; int primes[MAXN 5], p_cnt 0; bool is_prime[MAXN 5]; void euler_sieve_phi(int n) { // 初始化 for (int i 2; i n; i) is_prime[i] true; phi[1] 1; // 特别规定 is_prime[1] false; for (int i 2; i n; i) { if (is_prime[i]) { primes[p_cnt] i; // i是质数加入质数表 phi[i] i - 1; // 质数的欧拉函数值 } // 用当前已得到的质数 primes[j] 去筛掉 i * primes[j] for (int j 0; j p_cnt i * primes[j] n; j) { is_prime[i * primes[j]] false; // 标记合数 if (i % primes[j] 0) { // 情况1primes[j] 是 i 的最小质因子 phi[i * primes[j]] phi[i] * primes[j]; break; // 关键保证每个数只被其最小质因子筛掉一次 } else { // 情况2primes[j] 与 i 互质 phi[i * primes[j]] phi[i] * (primes[j] - 1); } } } }关键点与避坑指南phi[1] 1的初始化这是定义必须单独设置。筛法循环从2开始。break语句这是保证算法线性的关键。当i % primes[j] 0时说明 primes[j] 已经是 i 的最小质因子。那么对于后续更大的质数 primes[j1]i * primes[j1]的最小质因子应该是 primes[j] 而不是 primes[j1]。如果继续用 primes[j1] 去筛就会导致这个数被重复标记破坏线性复杂度。因此必须break。数组大小phi、primes、is_prime数组大小至少为 N1。对于很大的 N如 10^7要注意内存占用。结果验证可以计算几个小值如 φ(2), φ(6), φ(12)与公式法结果对比验证筛法正确性。3.4 方法对比与选用策略为了更直观地选择我将四种方法包括基于质数表的改进公式法总结如下方法时间复杂度 (单点)时间复杂度 (1~N)空间复杂度适用场景优点缺点递推法O(n log n)O(N² log N)O(1)教学、极小nn100实现简单贴近定义效率极低无法实用公式法O(√n)O(N√N)O(1)计算单个或少量n效率较高实现简单批量计算时重复分解质因数效率低线性筛法-O(N)O(N)批量计算1~N所有φ值线性时间复杂度效率最高需要额外O(N)空间代码稍复杂基于筛法的质数表O(质因子个数)O(N log log N)O(N)需要频繁计算不同n的φ值预处理后单点查询极快需要预处理筛出质数表选用策略建议竞赛或面试中单点查询无脑用公式法。代码短不易错效率足够应对大多数约束n ≤ 10^9。需要预处理1到N所有值必须用线性筛法。这是标准做法务必掌握。需要频繁计算大量随机大数的φ值且N很大可以先用线性筛法筛出足够大的质数表比如到 √(最大值)然后对于每个查询 n用质数表中的质数去试除分解实现加速的公式法。这是一种空间换时间的折中。4. 典型应用场景与问题剖析理解了怎么算更要明白为什么算。欧拉函数在多个领域有深刻应用下面通过几个典型问题来感受它的力量。4.1 应用一求解模运算下的乘法逆元费马小定理与欧拉定理在模运算中如果我们要计算 (a / b) mod m不能直接除法需要找到 b 在模 m 下的乘法逆元 b⁻¹使得 b * b⁻¹ ≡ 1 (mod m)然后计算 a * b⁻¹ mod m。费马小定理如果 m 是质数且 a 不是 m 的倍数那么 a^(m-1) ≡ 1 (mod m)。由此可得b 的逆元就是 b^(m-2) mod m。欧拉定理这是费马小定理的推广。如果 a 与 m 互质那么 a^φ(m) ≡ 1 (mod m)。由此可得b 的逆元是 b^(φ(m)-1) mod m。实操案例计算 7 在模 15 下的逆元。 首先15不是质数不能用费马小定理。计算 φ(15) φ(35) 15(1-1/3)*(1-1/5)8。因为 gcd(7,15)1满足欧拉定理条件。所以逆元为 7^(8-1) 7^7 mod 15。 我们可以通过快速幂计算7^249≡4 (mod15), 7^4≡4^216≡1 (mod15), 7^77^4 * 7^2 * 7^1 ≡ 1 * 4 * 7 28 ≡ 13 (mod15)。验证7 * 13 91, 91 mod 15 1。正确。心得当模数 m 不是质数但需要求逆元时欧拉定理是通用工具。前提是 a 与 m 互质。如果不互质则乘法逆元不存在。4.2 应用二RSA加密算法中的关键角色RSA公钥加密算法的核心数学基础依赖于欧拉函数。简单来说选择两个大质数 p 和 q计算 n p * q。n 是公钥和私钥的一部分。计算 n 的欧拉函数值 φ(n) (p-1)(q-1)。这个 φ(n) 是绝对保密的是私钥的核心。选择一个整数 e满足 1 e φ(n)且 e 与 φ(n) 互质。e 是公钥的一部分。计算 e 对于模 φ(n) 的乘法逆元 d即满足 e*d ≡ 1 (mod φ(n))。d 是私钥的另一部分。加密过程密文 C M^e mod n (M是明文)。 解密过程明文 M C^d mod n。其正确性由欧拉定理保证。因为 M 与 n 大概率互质如果不互质有极大概率分解n那RSA就被破解了所以有 M^φ(n) ≡ 1 (mod n)。而 ed ≡ 1 (mod φ(n)) 意味着 ed kφ(n) 1。因此解密时 C^d ≡ (M^e)^d ≡ M^(ed) ≡ M^(k*φ(n)1) ≡ (M^φ(n))^k * M ≡ 1^k * M ≡ M (mod n)。从这个过程可以看到φ(n) 的计算即 (p-1)(q-1)是连接公钥 e 和私钥 d 的桥梁。不知道 φ(n)也就不知道 p 和 q就无法从 e 推导出 d从而保证了安全性。4.3 应用三既约真分数计数与分数数列这是一个经典的组合数学问题以 n 为分母的所有既约真分数分子小于分母且互质有多少个答案就是 φ(n)。例如分母为 12 的既约真分数有1/12, 5/12, 7/12, 11/12共 4 个φ(12)4。进一步如果考虑所有分母不超过 N 的既约真分数并将其从小到大排列就构成了法里数列Farey Sequence。法里数列的许多性质也与欧拉函数和前缀和有关。例如法里数列 F_N 的长度分数个数为 1 Σ_{i1}^{N} φ(i)。这个公式在解决一些数学问题时非常有用。5. 常见问题、调试技巧与性能优化在实际编码和解题中会遇到一些典型问题。这里分享我的排查心得。5.1 数值溢出问题这在计算 φ(n) 或利用其进行幂运算时非常常见。公式法中的乘法ans ans / i * (i - 1)顺序很重要。必须先做除法再做乘法。如果写成ans ans * (i-1) / i虽然数学上等价但ans * (i-1)可能会在除法前就溢出整数范围。确保使用能容纳足够大整数的类型如 C 中的long long。线性筛法中的乘法在循环i * primes[j]时即使 i 和 primes[j] 本身是 int乘积也可能溢出 int。判断条件i * primes[j] n中的乘法可能在上限检查前就溢出了。安全的写法是primes[j] n / i用除法代替乘法进行判断。幂运算中的模乘在应用欧拉定理计算 a^b mod m 时必须使用快速幂算法并在乘法过程中每一步都取模防止中间结果溢出。5.2 边界条件与特殊输入处理n 1根据定义φ(1) 1。在公式法中循环不会执行最后temp等于1if (temp 1)不成立ans保持为初始值 n1正确。在线性筛法中需要显式设置phi[1] 1。n 是质数公式法会顺利执行因为循环内找不到因子最后temp n 1执行ans ans / n * (n - 1)得到 n-1正确。线性筛法中质数会被识别并设置phi[i] i - 1。n 非常大接近 int 上限在公式法求 sqrt(n) 时i * i n中的i*i可能溢出。最好将条件写成i n / i。或者使用long long类型的循环变量。5.3 线性筛法的实现细节与调试线性筛法代码相对复杂容易出错。调试时可以从简单数据开始。验证质数筛先注释掉所有与phi相关的计算只运行筛质数的部分输出质数表检查是否正确例如N30。验证 φ 值对小范围 N如 N10手动计算每个 φ(i)与程序输出的phi[i]对比。重点检查break条件这是保证线性的关键。可以打印出 i 和 primes[j] 的值观察每个合数是否只被其最小质因子筛掉一次。例如对于合数 12应该只在 i6, primes[j]2 时被标记而不应该在 i4, primes[j]3 时再次标记。数组初始化确保is_prime数组初始化为 truephi[1]1。primes数组清空。5.4 性能优化实践公式法的优化在 for 循环中步长可以设为 2只检查奇数因子因为除了2以外的偶数都不是质数。找到第一个因子后可以提前处理2这个特例。int euler_phi_optimized(int n) { int ans n; // 处理质因子2 if (n % 2 0) { ans ans / 2; // 等价于 ans ans / 2 * (2-1) while (n % 2 0) n / 2; } // 只检查奇数因子 for (int i 3; i n / i; i 2) { if (n % i 0) { ans ans / i * (i - 1); while (n % i 0) n / i; } } if (n 1) ans ans / n * (n - 1); return ans; }线性筛法的内存与速度权衡如果 N 非常大例如 10^8is_prime布尔数组可以用bitset容器来存储能将内存消耗减少到原来的 1/8。但访问速度会稍慢一些。phi数组如果不需要保存所有值例如只求前缀和可以用vector动态调整大小。掌握欧拉函数不仅仅是记住一个公式或一个算法更是理解一种将复杂问题分解为质因数幂次这一基本单元的数学思想。从基本的互质计数到支撑起现代网络安全的 RSA 算法其影响力贯穿始终。我个人的体会是多动手实现几次线性筛法并尝试用它去解决几道相关的编程题目比如求 Σφ(i) 的前缀和比只看理论理解要深刻得多。当你能够不参考模板独立写出正确高效的线性筛求 φ 函数代码时你对数论的理解就真正上了一个台阶。最后一个小技巧在调试数论代码时养成对拍的习惯——用公式法或暴力法生成小数据与你的优化算法如筛法结果对比能快速定位逻辑错误。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价