找回密码
 立即注册
首页 业界区 业界 快速幂算法的基础和扩展

快速幂算法的基础和扩展

忙贬 7 天前
快速幂

快速幂(Fast Exponentiation)算法解决这样一个问题:求解自然数的指数运算。计算 \(a^b\) 时,按照指数定义的朴素的方法是通过连续相乘:

\[a^b = \underbrace{a \times a \times \cdots \times a}_{b\text{次}}\]
这种方法需要进行 \(b-1\) 次乘法,当 \(b\) 很大时(如 \(10^9\)),时间复杂度 \(O(b)\) 是完全不可接受的。
快速幂通过巧妙的二进制分解技术,将幂运算的时间复杂度从 \(O(b)\) 优化到 \(O(\log b)\)。
考虑计算 \(a^{13}\),将指数 13 用二进制表示:

\[13 = 1101_2 = 2^3 + 2^2 + 0 + 2^0 = 8 + 4 + 0 + 1\]
因此:

\[a^{13} = a^{8 + 4 + 0 + 1} = a^8 \times a^4 \times a^0 \times a^1\]
而 $ a^8 = (a^4) ^2 = ((a^2) ^2) ^2$ ,分解后的幂次很容易计算
算法流程:

  • 初始化结果 1
  • 从最低位开始检查指数的二进制位
  • 如果当前位为 1,将当前的底数(\(a^{2^x}\))乘入结果
  • 底数不断平方(不断计算 \(a^0,a^1,a^2...\)),指数右移一位
  • 重复直到指数的最高位 1 也被遍历
快速幂算法也可以从递归的角度来理解,这种理解方式更加直观。

\[a^b = \begin{cases}1 & \text{if } b = 0 \\(a^{b/2})^2 & \text{if } b \text{ is even} \\a \times (a^{(b-1)/2})^2 & \text{if } b \text{ is odd}\end{cases}\]
  1. long long quick_pow(long long base, long long exp) {
  2.     long long res = 1;
  3.     while (exp) {
  4.         if (exp & 1) {
  5.             res *= base;
  6.         }
  7.         base *= base;
  8.         exp >>= 1;
  9.     }
  10.     return res;
  11. }
复制代码
带模数版本

更多的时候,我们要求解的是 \(a^b \bmod m\)。这也可以用快速幂思想解决。快速幂模数版本的正确性基于模运算的分配律:

\[(a \times b)\bmod m = [(a \bmod m) \times (b \bmod m)] \bmod m\]
因此,我们可以直接对 $ a $ 取模,同时在算法每一步中,我们都对中间结果取模,这保证了最终结果的正确性,同时防止数值溢出。
不过我们不能直接对指数取模。指数 \(b\) 必须保持原值,因为:

\[a^b \bmod m \neq a^{b \bmod m} \bmod m\]
当然,既然复杂度是对数的,所以 \(b\) 大一些一般也无所谓。
  1. long long quick_pow(long long base, long long exp, long long mod) {
  2.     long long res = 1;
  3.     base %= mod;  // 先取模,防止初始base过大
  4.     while (exp) {
  5.         if (exp & 1) {
  6.             res = (res * base) % mod;
  7.         }
  8.         base = (base * base) % mod;
  9.         exp >>= 1;
  10.     }
  11.     return res;
  12. }
复制代码
不过,在某些特定情况下,我们可以使用欧拉定理来化简指数:
欧拉定理:如果 \(a\) 和 \(m\) 互质(即 \(\gcd(a, m) = 1\)),那么:

\[a^{\phi(m)} \equiv 1 \pmod{m}\]
其中 \(\phi(m)\) 是欧拉函数,表示小于 \(m\) 且与 \(m\) 互质的正整数的个数。
所以当 \(a\) 和 \(m\) 互质时,我们可以将指数对 \(\phi(m)\) 取模:

\[a^b \mod m = a^{b \mod \phi(m)} \mod m\]
这在某些数学和密码学应用中很有用,但不是快速幂算法的必要部分。代码略。
快速幂方法的时间复杂度是 \(O(\log b)\),循环次数等于指数的二进制位数,效率极高。
演示:计算 \(3^{13} \mod 100\)
  1. 指数 13 = 1101(二进制)
  2. 初始化: res = 1, base = 3
  3. 第1轮 (最低位为1): res = 1×3 = 3, base = 3² = 9
  4. 第2轮 (位为0):    res = 3,     base = 9² = 81  
  5. 第3轮 (位为1):    res = 3×81 = 243 ≡ 43, base = 81² = 6561 ≡ 61
  6. 第4轮 (位为1):    res = 43×61 = 2623 ≡ 23
  7. 结果: 3¹³ ≡ 23 (mod 100)
复制代码
快速乘(防治溢出)

同样的思想也可以应用到乘法本身中。两个 32 位整数相乘,范围将达到 64 位;两个 64 位整数相乘,范围将达到 128 位。同样大小的数无法装入正确的结果。
快速乘(又称"龟速乘")模仿快速幂的思想,将乘法运算转换为加法运算。核心思路是将 \(a \times b\) 看作是 \(b\) 个 \(a\) 相加,然后利用二进制分解来优化,这样就可以在中间结果下取模。
对于 \(a \times b\),将 \(b\) 二进制分解:

\[b = \sum_{i=0}^{k} b_i \cdot 2^i \quad \text{其中 } b_i \in \{0,1\}\]
那么:

\[a \times b = a \times \sum_{i=0}^{k} b_i \cdot 2^i = \sum_{i=0}^{k} b_i \cdot (a \cdot 2^i)\]
  1. typedef long long ll;
  2. // 快速乘:返回 (a * b) % mod,防止中间过程溢出
  3. ll quick_mul(ll a, ll b, ll mod) {
  4.     ll res = 0;
  5.     a %= mod;
  6.     while (b) {
  7.         if (b & 1) {
  8.             res = (res + a) % mod;
  9.         }
  10.         a = (a + a) % mod;  // a = 2a,相当于左移一位
  11.         b >>= 1;
  12.     }
  13.     return res;
  14. }
  15. // 使用快速乘的快速幂
  16. ll quick_pow_safe(ll base, ll exp, ll mod) {
  17.     ll res = 1;
  18.     base %= mod;
  19.     while (exp) {
  20.         if (exp & 1) {
  21.             res = quick_mul(res, base, mod);  // 关键替换!
  22.         }
  23.         base = quick_mul(base, base, mod);    // 关键替换!
  24.         exp >>= 1;
  25.     }
  26.     return res;
  27. }
复制代码
方法时间复杂度空间复杂度防溢出能力直接乘法\(O(1)\)\(O(1)\)无快速乘\(O(\log n)\)\(O(1)\)有快速乘通过 \(O(\log n)\) 次加法替代 \(O(1)\) 次乘法,实际上更慢了,所以也叫做“龟速乘”,这属于用时间换取了数值安全性。
浮点幂

如果是底数浮点,指数自然数,那么直接应用快速幂没有任何问题。但若指数是浮点数,这个问题会麻烦的多:浮点数指数无法直接进行二进制位操作,且误差会随着运算的拆分不断累积。
相比之下浮点幂的主要思想是利用自然对数变换法来计算浮点幂:

\[a^b = e^{b \cdot \ln(a)}\]
其中,自然对数和指数是常见且重要的函数,有快速且精确的办法来实现。
常见的库(如C++ 、Intel MKL、GNU Scientific Library)采用此类核心思路。
  1. // 伪代码示意
  2. if (a == 0.0) {
  3.     if (b > 0) return 0.0;
  4.     if (b == 0) return 1.0;  // 或 NaN,依标准而定
  5.     return INFINITY;         // 或报错
  6. }
  7. if (a == 1.0) return 1.0;
  8. if (b == 0.0) return 1.0;
  9. if (b == 1.0) return a;
  10. result = exp(b * log(a)); # 对于一般情况
复制代码
矩阵快速幂

已知矩阵 \(A\),由于矩阵乘法满足结合律,指数为自然数时,仍可以利用快速幂思想求解 \(A^n\)。这最其深刻、最实用的扩展之一。它将快速幂的核心理念从标量运算成功迁移到了线性代数领域。

\[A^n = \begin{cases} I & \text{if } n = 0 \\(A^{n/2})^2 & \text{if } n \text{ is even} \\A \times (A^{(n-1)/2})^2 & \text{if } n \text{ is odd}\end{cases}\]
  1. typedef vector<vector<long long>> Matrix;
  2. Matrix matrixMultiply(const Matrix& A, const Matrix& B, long long mod) {
  3.     int n = A.size();
  4.     Matrix C(n, vector<long long>(n, 0));
  5.     for (int i = 0; i < n; i++) {
  6.         for (int j = 0; j < n; j++) {
  7.             for (int k = 0; k < n; k++) {
  8.                 C[i][j] = (C[i][j] + A[i][k] * B[k][j]) % mod;
  9.             }
  10.         }
  11.     }
  12.     return C;
  13. }
  14. Matrix matrixPow(Matrix base, long long exp, long long mod) {
  15.     int n = base.size();
  16.     // 初始化单位矩阵
  17.     Matrix res(n, vector<long long>(n, 0));
  18.     for (int i = 0; i < n; i++) {
  19.         res[i][i] = 1;
  20.     }
  21.    
  22.     while (exp > 0) {
  23.         if (exp & 1) {
  24.             res = matrixMultiply(res, base, mod);
  25.         }
  26.         base = matrixMultiply(base, base, mod);
  27.         exp >>= 1;
  28.     }
  29.     return res;
  30. }
复制代码
经典案例:斐波那契数列的矩阵解法

斐波那契数列的递推关系:

\[F(n) = F(n-1) + F(n-2)\]
可以表示为矩阵形式:

\[\begin{bmatrix} F(n) \\ F(n-1) \end{bmatrix} = \begin{bmatrix} 1 & 1 \\ 1 & 0 \end{bmatrix} \times \begin{bmatrix} F(n-1) \\ F(n-2) \end{bmatrix}\]
递推得到:

\[\begin{bmatrix} F(n) \\ F(n-1) \end{bmatrix} = \begin{bmatrix} 1 & 1 \\ 1 & 0 \end{bmatrix}^{n-1} \times \begin{bmatrix} F(1) \\ F(0) \end{bmatrix}\]
由于我们可以快速计算矩阵的幂,我们就绕过了斐波那契数列的定义,使用对数次矩阵乘法的时间直接计算出了某一项。
  1. long long fibonacci_matrix(long long n, long long mod) {
  2.     if (n == 0) return 0;
  3.     if (n == 1) return 1;
  4.    
  5.     Matrix base = {{1, 1}, {1, 0}};
  6.     Matrix result = matrixPow(base, n - 1, mod);
  7.     return result[0][0];  // F(n)
  8. }
复制代码
更一般的,对于 k 阶线性递推:

\[a_n = c_1a_{n-1} + c_2a_{n-2} + \cdots + c_ka_{n-k}\]
构造转移矩阵:

\[M = \begin{bmatrix}c_1 & c_2 & \cdots & c_{k-1} & c_k \\1 & 0 & \cdots & 0 & 0 \\0 & 1 & \cdots & 0 & 0 \\\vdots & \vdots & \ddots & \vdots & \vdots \\0 & 0 & \cdots & 1 & 0\end{bmatrix}\]
则:

\[\begin{bmatrix} a_n \\ a_{n-1} \\ \vdots \\ a_{n-k+1} \end{bmatrix} = M^{n-k+1} \times \begin{bmatrix} a_{k-1} \\ a_{k-2} \\ \vdots \\ a_0 \end{bmatrix}\]

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册