引言
快速幂(Exponentiation by Squaring)又称”平方求幂”,是将 O(n) 复杂度的幂运算优化为 O(log n) 的经典算法。其核心思想简单而优雅:利用指数的二进制表示,将幂运算拆解为一系列平方与乘法操作。该算法不仅本身极具教学价值,更是密码学(如 RSA)、组合数学、动态规划优化中的基础工具。
本文将用 Java 从零实现快速幂算法,从基础递归版、迭代优化版、矩阵快速幂到工程应用,完整呈现这一算法的设计与实现。
问题引入
计算 a^n 最直观的方法是连乘 n 次:
public static long powerNaive(long a, long n) {
long result = 1;
for (int i = 0; i < n; i++) {
result *= a;
}
return result;
}
对于 n = 10^9 这种规模,需要数十亿次乘法,性能与数值范围均不可接受。快速幂将时间复杂度降低到 O(log n),对 n = 10^9 仅需约 30 次操作。
核心思想:二进制分解
任何正整数 n 都可以唯一表示为二进制:
n = b_k × 2^k + b_{k-1} × 2^{k-1} + ... + b_1 × 2 + b_0
其中 b_i ∈ {0, 1}。由此:
a^n = a^(b_k × 2^k) × a^(b_{k-1} × 2^{k-1}) × ... × a^(b_0)
= ∏ (a^(2^i))^(b_i)
只需要从 a^1 开始,逐步平方得到 a^2, a^4, a^8, …, a^(2^k),再按二进制位累积结果。
递归实现
package com.algorithm.fastpower;
/**
* 快速幂基础实现
* 通过分治思想:a^n = (a^(n/2))^2 × (n 为奇数时再乘 a)
*/
public class FastPower {
/**
* 递归版快速幂:O(log n) 时间复杂度
* @param a 底数
* @param n 指数(非负整数)
* @return a^n
*/
public static long powerRecursive(long a, long n) {
if (n < 0) throw new IllegalArgumentException("指数必须非负");
if (n == 0) return 1;
if (n == 1) return a;
long half = powerRecursive(a, n / 2);
long result = half * half;
if (n % 2 == 1) {
result *= a;
}
return result;
}
/**
* 迭代版快速幂:避免递归栈开销
* 通过遍历 n 的二进制位来累积结果
*/
public static long powerIterative(long a, long n) {
if (n < 0) throw new IllegalArgumentException("指数必须非负");
if (n == 0) return 1;
long result = 1;
long base = a;
long exp = n;
while (exp > 0) {
if ((exp & 1) == 1) {
result *= base;
}
base *= base;
exp >>= 1;
}
return result;
}
/**
* 大整数快速幂(使用 BigInteger)
* 适用于超出 long 范围的场景
*/
public static java.math.BigInteger powerBigInteger(
java.math.BigInteger a, long n) {
if (n < 0) throw new IllegalArgumentException("指数必须非负");
java.math.BigInteger result = java.math.BigInteger.ONE;
java.math.BigInteger base = a;
long exp = n;
while (exp > 0) {
if ((exp & 1) == 1) {
result = result.multiply(base);
}
base = base.multiply(base);
exp >>= 1;
}
return result;
}
}
模幂运算
计算 a^n mod m 是密码学的基础操作。如果先计算 a^n 再取模,数值会爆炸。必须将取模嵌入到每次乘法中。
package com.algorithm.fastpower;
import java.math.BigInteger;
/**
* 模幂运算:a^n mod m
* 关键技巧:每步乘法后立即取模,避免数值爆炸
*/
public class ModularPower {
/**
* 基础模幂:long 范围
*/
public static long modPower(long a, long n, long m) {
if (m == 1) return 0;
if (n < 0) throw new IllegalArgumentException("指数必须非负");
long result = 1;
a = ((a % m) + m) % m; // 处理负数底数
long base = a;
long exp = n;
while (exp > 0) {
if ((exp & 1) == 1) {
result = (result * base) % m;
}
base = (base * base) % m;
exp >>= 1;
}
return result;
}
/**
* 大整数模幂
* BigInteger 的 modPow 已内部优化,但理解原理重要
*/
public static BigInteger modPowerBig(BigInteger a, BigInteger n, BigInteger m) {
if (m.signum() <= 0) throw new IllegalArgumentException("模数必须为正");
if (n.signum() < 0) throw new IllegalArgumentException("指数必须非负");
BigInteger result = BigInteger.ONE;
BigInteger base = a.mod(m);
int bitLength = n.bitLength();
for (int i = 0; i < bitLength; i++) {
if (n.testBit(i)) {
result = result.multiply(base).mod(m);
}
base = base.multiply(base).mod(m);
}
return result;
}
/**
* 蒙哥马利模幂:进一步优化大整数模运算
* 核心思想:将普通乘法转换为蒙哥马利乘法,避开昂贵的除法
*/
public static BigInteger montgomeryPower(BigInteger a, BigInteger n, BigInteger m) {
if (m.signum() <= 0) throw new IllegalArgumentException("模数必须为正");
// 蒙哥马利参数 R = 2^k > m
int k = (m.bitLength() + 7) / 8 * 8 + 8;
BigInteger R = BigInteger.ONE.shiftLeft(k);
BigInteger Rinv = R.modInverse(m);
BigInteger mNegInv = R.subtract(m.modInverse(R)).mod(R);
// 转换 a 到蒙哥马利域
BigInteger aMont = a.multiply(R).mod(m);
BigInteger resultMont = R.mod(m);
int bitLength = n.bitLength();
for (int i = bitLength - 1; i >= 0; i--) {
// result = result^2
resultMont = montgomeryMul(resultMont, resultMont, m, R, mNegInv);
if (n.testBit(i)) {
resultMont = montgomeryMul(resultMont, aMont, m, R, mNegInv);
}
}
// 转换回普通域
return montgomeryMul(resultMont, BigInteger.ONE, m, R, mNegInv);
}
private static BigInteger montgomeryMul(BigInteger x, BigInteger y,
BigInteger m, BigInteger R,
BigInteger mNegInv) {
BigInteger t = x.multiply(y);
BigInteger u = t.multiply(mNegInv).and(R.subtract(BigInteger.ONE));
BigInteger candidate = t.add(u.multiply(m)).shiftRight(R.bitLength() - 1);
if (candidate.compareTo(m) >= 0) {
candidate = candidate.subtract(m);
}
return candidate;
}
}
矩阵快速幂
矩阵快速幂将快速幂思想扩展到矩阵乘法,用于加速递推关系(如斐波那契数列)。
package com.algorithm.fastpower;
import java.util.Arrays;
/**
* 矩阵快速幂
* 应用场景:递推关系加速,如斐波那契数列从 O(n) 优化到 O(log n)
*/
public class MatrixPower {
/**
* 2x2 矩阵乘法
*/
public static long[][] multiply(long[][] a, long[][] b, long mod) {
int n = a.length;
long[][] result = new long[n][n];
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
long sum = 0;
for (int k = 0; k < n; k++) {
sum = (sum + a[i][k] * b[k][j]) % mod;
}
result[i][j] = sum;
}
}
return result;
}
/**
* 矩阵快速幂
*/
public static long[][] matrixPower(long[][] base, long n, long mod) {
int size = base.length;
// 初始化为单位矩阵
long[][] result = new long[size][size];
for (int i = 0; i < size; i++) result[i][i] = 1;
long[][] cur = base;
long exp = n;
while (exp > 0) {
if ((exp & 1) == 1) {
result = multiply(result, cur, mod);
}
cur = multiply(cur, cur, mod);
exp >>= 1;
}
return result;
}
/**
* 应用 1:斐波那契数列 F(n) = F(n-1) + F(n-2)
* 转移矩阵:[F(n) ] [1 1] [F(n-1)]
* [F(n-1)] = [1 0] [F(n-2)]
*/
public static long fibonacci(long n, long mod) {
if (n == 0) return 0;
if (n == 1) return 1;
long[][] base = {{1, 1}, {1, 0}};
long[][] result = matrixPower(base, n - 1, mod);
return result[0][0]; // F(n) = result[0][0]
}
/**
* 应用 2:线性递推 a(n) = 2*a(n-1) + 3*a(n-2) + 5*a(n-3)
* 转移矩阵为 3x3
*/
public static long linearRecurrence(long n, long mod) {
if (n == 0) return 1; // 初始值 a(0)
if (n == 1) return 2;
if (n == 2) return 3;
long[][] base = {
{2, 3, 5},
{1, 0, 0},
{0, 1, 0}
};
long[][] result = matrixPower(base, n - 1, mod);
return result[0][0];
}
}
应用案例
案例 1:斐波那契数列
package com.algorithm.fastpower;
import java.math.BigInteger;
/**
* 斐波那契数列:递归与矩阵快速幂对比
*/
public class Fibonacci {
public static void main(String[] args) {
long n = 1000000000000L; // 10^12
// 矩阵快速幂:O(log n)
long mod = 1_000_000_007L;
long result = MatrixPower.fibonacci(n, mod);
System.out.println("F(" + n + ") mod 10^9+7 = " + result);
// 大整数场景
BigInteger bigResult = fibonacciBig(BigInteger.valueOf(n));
System.out.println("F(" + n + ") = " + bigResult.toString().substring(0, 50) + "...");
}
/**
* 大整数斐波那契(无取模)
*/
public static BigInteger fibonacciBig(BigInteger n) {
if (n.equals(BigInteger.ZERO)) return BigInteger.ZERO;
if (n.equals(BigInteger.ONE)) return BigInteger.ONE;
BigInteger[][] base = {
{BigInteger.ONE, BigInteger.ONE},
{BigInteger.ONE, BigInteger.ZERO}
};
BigInteger[][] result = matrixPowerBig(base, n.subtract(BigInteger.ONE));
return result[0][0];
}
private static BigInteger[][] matrixPowerBig(BigInteger[][] base, BigInteger n) {
int size = base.length;
BigInteger[][] result = new BigInteger[size][size];
for (int i = 0; i < size; i++) {
for (int j = 0; j < size; j++) {
result[i][j] = (i == j) ? BigInteger.ONE : BigInteger.ZERO;
}
}
BigInteger[][] cur = new BigInteger[size][size];
for (int i = 0; i < size; i++) {
for (int j = 0; j < size; j++) {
cur[i][j] = base[i][j];
}
}
BigInteger exp = n;
while (exp.signum() > 0) {
if (exp.testBit(0)) {
result = multiplyBig(result, cur);
}
cur = multiplyBig(cur, cur);
exp = exp.shiftRight(1);
}
return result;
}
private static BigInteger[][] multiplyBig(BigInteger[][] a, BigInteger[][] b) {
int n = a.length;
BigInteger[][] result = new BigInteger[n][n];
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
BigInteger sum = BigInteger.ZERO;
for (int k = 0; k < n; k++) {
sum = sum.add(a[i][k].multiply(b[k][j]));
}
result[i][j] = sum;
}
}
return result;
}
}
案例 2:RSA 加密核心
package com.algorithm.fastpower;
import java.math.BigInteger;
import java.security.SecureRandom;
/**
* 简化版 RSA 加密演示
* 核心:使用快速模幂实现加密/解密
*/
public class SimpleRSA {
private final BigInteger n, e, d;
public SimpleRSA(int keySize) {
SecureRandom random = new SecureRandom();
BigInteger p = BigInteger.probablePrime(keySize / 2, random);
BigInteger q = BigInteger.probablePrime(keySize / 2, random);
n = p.multiply(q);
BigInteger phi = p.subtract(BigInteger.ONE).multiply(q.subtract(BigInteger.ONE));
e = BigInteger.valueOf(65537);
d = e.modInverse(phi);
}
/**
* 加密:c = m^e mod n(快速模幂)
*/
public BigInteger encrypt(BigInteger message) {
return ModularPower.modPowerBig(message, e, n);
}
/**
* 解密:m = c^d mod n(快速模幂)
*/
public BigInteger decrypt(BigInteger ciphertext) {
return ModularPower.modPowerBig(ciphertext, d, n);
}
public static void main(String[] args) {
SimpleRSA rsa = new SimpleRSA(1024);
BigInteger message = BigInteger.valueOf(123456789);
long start = System.nanoTime();
BigInteger cipher = rsa.encrypt(message);
long encTime = (System.nanoTime() - start) / 1000;
start = System.nanoTime();
BigInteger decrypted = rsa.decrypt(cipher);
long decTime = (System.nanoTime() - start) / 1000;
System.out.println("原文: " + message);
System.out.println("密文: " + cipher);
System.out.println("解密: " + decrypted);
System.out.println("加密耗时: " + encTime + " μs");
System.out.println("解密耗时: " + decTime + " μs");
}
}
案例 3:模逆元求解
费马小定理:当模数 m 是素数时,a 的模逆元 = a^(m-2) mod m。
package com.algorithm.fastpower;
import java.math.BigInteger;
/**
* 模逆元的快速幂求解
*/
public class ModularInverse {
/**
* 费马小定理求逆元(要求 m 是素数)
* a^(-1) ≡ a^(m-2) (mod m)
*/
public static long inverseByPower(long a, long prime) {
return ModularPower.modPower(a, prime - 2, prime);
}
public static BigInteger inverseByPowerBig(BigInteger a, BigInteger prime) {
return ModularPower.modPowerBig(a, prime.subtract(BigInteger.TWO), prime);
}
public static void main(String[] args) {
long a = 3, m = 7;
long inv = inverseByPower(a, m);
System.out.println(a + "^(-1) mod " + m + " = " + inv);
System.out.println("验证: " + a + " × " + inv + " mod " + m + " = " +
(a * inv) % m);
}
}
性能基准
package com.algorithm.fastpower;
/**
* 快速幂 vs 朴素幂运算性能对比
*/
public class PerformanceBenchmark {
public static void main(String[] args) {
// 测试 1:long 范围
int[] exponents = {10, 100, 1_000, 1_000_000, 1_000_000_000};
long base = 2;
System.out.println("=== long 范围性能测试 ===");
System.out.printf("%-12s %-15s %-15s %-10s%n", "指数 n", "朴素 (ms)", "快速幂 (ms)", "加速比");
for (int n : exponents) {
if (n > 62) break; // 避免溢出
long start = System.nanoTime();
long r1 = powerNaive(base, n);
long t1 = (System.nanoTime() - start) / 1_000_000;
start = System.nanoTime();
long r2 = FastPower.powerIterative(base, n);
long t2 = (System.nanoTime() - start) / 1_000_000;
System.out.printf("%-12d %-15d %-15d %-10.0f%n", n, t1, t2,
t1 > 0 ? (double) t1 / Math.max(t2, 1) : 0);
}
// 测试 2:斐波那契
System.out.println("\n=== 斐波那契性能测试 ===");
long[] fibs = {1_000L, 100_000L, 1_000_000_000L, 1_000_000_000_000L};
long mod = 1_000_000_007L;
for (long n : fibs) {
long start = System.nanoTime();
long fib = MatrixPower.fibonacci(n, mod);
long time = (System.nanoTime() - start) / 1_000_000;
System.out.printf("F(%d) mod 10^9+7 = %d, 耗时 %d ms%n", n, fib, time);
}
}
private static long powerNaive(long a, int n) {
long result = 1;
for (int i = 0; i < n; i++) result *= a;
return result;
}
}
复杂度分析
| 算法 | 时间复杂度 | 空间复杂度 | 适用场景 |
|---|---|---|---|
| 朴素幂 | O(n) | O(1) | 教学演示 |
| 递归快速幂 | O(log n) | O(log n) 栈 | 简单场景 |
| 迭代快速幂 | O(log n) | O(1) | 工程首选 |
| 模幂 | O(log n × M(k)) | O(1) | 密码学 |
| 矩阵快速幂 | O(k³ log n) | O(k²) | 递推关系 |
| 蒙哥马利模幂 | O(k² log n) | O(1) | 高性能密码学 |
其中 M(k) 是 k 位数乘法的复杂度(k 位快速傅里叶变换可达 O(k log k))。
工程实践要点
- 处理负指数:a^(-n) = 1/a^n,非整数场景下不适用
- 取模时机:每步乘法后立即取模,避免数值爆炸
- 溢出防护:使用 BigInteger 处理大整数场景
- 缓存结果:相同 (a, n) 组合的重复计算可缓存
- SIMD 加速:现代 CPU 可并行多个模幂运算
扩展话题
快速乘
将 64 位整数乘法分解为多个 32 位乘法和加法,避免溢出。Karatsuba 进一步优化到 O(k^1.585)。
蒙哥马利乘法
避免昂贵的除法运算,将模运算转化为移位与加法。RSA 等密码学库普遍使用。
滑动窗口算法
通过预计算小幅度的幂(如 1, 2, 4, 8…)减少乘法次数。窗口大小通常为 4-7。
预计算与查表
对于固定底数(如 2 的幂),可预计算所有结果并查表,O(1) 返回。
应用场景
- RSA、Diffie-Hellman、ElGamal:公钥密码学
- 离散对数:Pollard Rho 等算法
- 斐波那契、组合数学:递推关系
- 概率算法:Miller-Rabin 素性测试
- 图论:邻接矩阵幂计算路径数
- 机器学习:Softmax 等指数运算
- 数值计算:矩阵指数 e^A
总结
快速幂是分治思想在数值计算中的经典应用。短短几行代码即可将 O(n) 优化为 O(log n),其影响远超算法本身——它支撑着现代密码学与高性能计算的核心。掌握快速幂不仅能解决”求幂”问题,更能体会到分治、递推、二进制表示这些基础思想在工程中的强大威力。
参考资料
- Knuth, D. E. (1997). The Art of Computer Programming, Volume 2: Seminumerical Algorithms. Addison-Wesley.
- Menezes, A. J., et al. (1996). Handbook of Applied Cryptography. CRC Press.
- Montgomery, P. L. (1985). Modular multiplication without trial division. Mathematics of Computation.
- Crandall, R., & Pomerance, C. (2005). Prime Numbers: A Computational Perspective. Springer.