矩阵快速幂

快速幂

如果希望求得一个数 \(a\)\(b\) 次幂,一般情况下,暴力的做法就是从 \(1\) 遍历到 \(b\),每次遍历时都将结果乘上 \(a\),得到最终结果。这种做法的时间复杂度为 \(O(n)\),在实际应用中如果对性能要求不是特别高的话,这样也是可行的。一种更为高效的计算方式是根据计算机存储数据的方式,通过巧妙的运算,可以显著地提升计算幂的计算效率。

具体解释如下:

对于任意的指数 \(b\),如果它是一个整数,那么它一定可以获得对应二进制的形式(\(10010\) 格式),而对于 \(a^b\) 计算来讲,那么它可以划分为如下的格式:

\[\begin{equation} \begin{split} a^b &=a^{b_{2}}\\ &= a^{000}*a^{001}*a^{010}…… \end{split} \end{equation} \]

举个例子,假设现在要计算 \(5^{10}\),那么可以划分为如下的形式:

\[\begin{equation} \begin{split} 5^{10} &= 5^{10_{2}}=5^{1010} \\ &=5^{2}*5^{8} \end{split} \end{equation} \]

而在计算的过程中,可以对高次幂的计算进行转换,转换为由低次幂累乘的结果:\(5^{8}=5^{4}*5^{4}\)\(5^{4}=5^2*5^2\),从而减少重复计算,提升计算效率

具体实现代码如下:

public long pow(int a, int b) {
    if (a == 1 || b <= 0) return 1;
    long ans = 1L, t = a;
    while (b > 0) {
        /*
        	判断当前所在位是否是幂的有效位,如果是有效位,则说明当前的累乘结果
        	是最终结果的一部分
        */
        if ((b & 1) != 0) {
            ans = ans * t;
        }
        /*
        	累乘操作,分别计算每个位对应的幂
        */
        t = t*t;
        b >>= 1;
    }
    return ans;
}

这种做法的时间复杂度为 \(O(log_{2}b)\)

矩阵快速幂

和上文中 “快速幂” 的思想类似,不过这里将计算的底数 \(a\) 换成了矩阵 \(M\),在矩阵的乘法操作中,可以大幅度降低需要的乘法次数

以求斐波那契数列为例,斐波那契数列值得是符合以下递推公式的数列:

\[\begin{equation} F(x)=\left\{ \begin{aligned} & 0 \qquad n = 0\\ & 1 \qquad n = 1 \\ & F(n - 1) + F(n - 2) \qquad n \geq 2 \end{aligned} \right. \end{equation} \]

一般情况下,当需要求的斐波那契数不是特别大的时候(如 \(n < 20\)),递归可以解决,当规模比较大的时候,使用动态规划的方式,在 \(n < 10^8\) 的情况下,依旧可以取得能够接受的性能。但是当 \(n\) 的数量大于 \(10^9\) 时,在现有的普通计算机的计算能力下,无法取得较好响应时间

一种优化的方式是结合矩阵的相关知识,在 \(n \geq 2\)的前提条件下, 将 \(F(n - 1)、F(n-2)\) 组合成为一个向量:

\[\begin{bmatrix} F(n-1)\\ F(n-2) \end{bmatrix} \]

那么,对于 \(F(n)\) 来将,它对应的向量为:

\[\begin{bmatrix} F(n)\\ F(n - 1) \end{bmatrix} \]

根据现有的递推关系,对其进行展开:

\[\begin{bmatrix} F(n)\\ F(n - 1) \end{bmatrix} = \begin{bmatrix} F(n - 1)*1 + F(n - 2)*1 \\ F(n-1)*1 + F(n - 2)*0 \end{bmatrix} \]

根据矩阵乘法,可以对上式进行分解:

\[\begin{bmatrix} F(n)\\ F(n - 1) \end{bmatrix} = \begin{bmatrix} 1\quad 1\\ 1\quad 0 \end{bmatrix} * \begin{bmatrix} F(n - 1) \\ F(n -2) \end{bmatrix} \]

\[Mat = \begin{bmatrix} 1\quad 1\\ 1\quad 0 \end{bmatrix} \]

则有

\[\begin{bmatrix} F(n) \\ F(n - 1) \end{bmatrix} = Mat^{n-1} * \begin{bmatrix} F(1) \\ F(0) \end{bmatrix} \]

而对于 \(Mat^{n-1}\) 的计算则可以用上面提到的快速幂进行计算

具体的示例代码如下:

public class Solution {
    private static final int mod = (int) 1e9 + 7;
    private static final int N = 2;
    
    long[][] mul(long[][] a, long[][] b) {
        long[][] c = new long[N][N];
        for (int i = 0; i < N; ++i) {
            for (int j = 0; j < N; ++j) {
                c[i][j] = a[i][0]*b[0][j] + a[i][1]*b[1][j];
                c[i][j] %= mod;
            }
        }
        return c;
    }
    
    public int cal(int n) {
        if (n == 0) return 0;
        if (n == 1) return 1;
        // 单位矩阵,相当于快速幂中的初始值 1
        long[][] ans = new long[][]{
            {1, 0},
            {0, 1}
        };
        long[][] mat = new long[][]{
            {1, 1},
            {1, 0}
        };
        int k = n - 1;
        while (k > 0) {
            if ((k & 1) != 0) ans = mul(ans, mat);
            mat = mul(mat, mat);
            k >>= 1;
        }
        return (int) ans[0][0];
    }
}

时间复杂度:\(O(log_{2}n)\) 为快速幂计算的复杂度

空间复杂度:\(O(1)\) 只需要常量的空间


参考:

[1] https://leetcode.cn/problems/n-th-tribonacci-number/solutions/922594/gong-shui-san-xie-yi-ti-si-jie-die-dai-d-m1ie/

[2] https://www.desgard.com/algo/docs/part2/ch01/3-matrix-quick-pow/

posted @ 2022-12-14 17:12  FatalFlower  阅读(29)  评论(0编辑  收藏  举报