数的快速幂与矩阵的快速幂

快速幂

对于任意底数的幂结果都可以分解为指数为2的幂的结果相乘。

因为任意一个数可以用二进制来表示,所以我们的指数(假设为b),可以写成

b = b 0 2 0 + b 1 2 1 + b 2 2 2 + . . . . . . + b n 2 n   ( 1
(其中,各子项的系数为0或1,因为二进制)

所以我们的

a b = a b 0 2 0 a b 1 2 1 a b 2 2 2 a b 0 2 0 . . . . . . a b n 2 n   2

 比如3^6 = 3^4*3^2 

所以我们可以利用以上的结论,思想分解我们的指数为2的幂的多项式的形式,然后依次从低位考察这个多项式,若二进制某一位为0,则该子项为0,那么以该子项为幂的结果就为1,所以我们跳过该位。我们还发现式子2的每一个项都是前一项的平方,所以我们在考察指数多项式的每一位时,不管该位是不是0,都应该记录该子项的结果,方便遇到二进制为1时直接累乘该值即可,减少相乘次数。具体算法如下:

/**
     * 快速幂
     * @param num 底数
     * @param n   指数
     * @return
     */
    public static int fastPower(int num, int n){
        int result = 1;
        int digit = num;
        while(n != 0){
            if((n & 1) == 1){  //该数位为1, 意思分解为幂相乘时,有该数
                result *= digit; 
            }
            n >>= 1;
            digit *= digit; //指数每右移一位,指数相当于乘以2,对应到幂结果应执行一次平方
        }
        return result;
    }

复杂度分析

普通求幂需要连乘n次,所以复杂度为O(n)。而采用快速幂,把数的相乘按照指数二进制形式的合并了,且合并规模的为上一次的二倍。比如求2的9次方,正常需要连乘9次,而采用快速幂可以降为6次。9的二进制为4位,故需要4次乘法记录当前中间值(该中间值主要为了减少乘法次数,方便遇到二进制为1使用该值),外加2次的二进制为1时需要乘以中间值。因为问题规模为自底向上倍增上去,故复杂度为O(logn)。

矩阵的快速幂

因为矩阵相乘满足结合率,所以,我们可以扩展到二维数组上,也就是求矩阵的幂。而且矩阵的快速幂原理与数的快速幂是一致的,都是利用指数的二进制表现形式,不同的是我们需额外实现矩阵相乘算法。

    /**
     * 矩阵快速幂
     * @param base
     * @param index
     * @return
     */
    public static int[][] fastPowMatrix(int[][] base, int index) {
        int[][] res = new int[base.length][base[0].length];
        for(int i = 0; i < base.length; i++) {
            res[i][i] = 1;
        }
        int[][] t = base;
        while(index != 0) {
            if((index & 1) == 1) {
                res = multipleMatix(res, t);
            }
            t = multipleMatix(t, t);
            index >>= 1;
        }
        return res;
    }

    /**
     * 矩阵相乘
     * @param left
     * @param right
     * @return
     */
    public static int[][] multipleMatix(int[][] left, int[][] right) {
        int[][] res = new int[left.length][left[0].length];
        for(int i = 0; i < left.length; i++) {
            for(int j = 0; j < right.length; j++) {
                for(int k = 0; k < left[0].length; k++) {
                    res[i][j] += left[i][k] * right[k][j];
                }
            }
        }
        return res;
    }

猜你喜欢

转载自blog.csdn.net/dawn_after_dark/article/details/80697483