带你走进快速幂,感叹算法的魅力

快速幂

快速幂是一种能够对m的n次幂快速进行计算的一种算法。能在log(n)的时间内求出结果。

传统算法

一般情况下我们会想到直接对mn次不就得到了m的n次幂了吗?也就是下面的这种算法,我们可以看到它的时间复杂度是O(n)

private static double power1(int a,int b){
        double res = 1;
        for (int i = 0; i < b ; i++){
            res *= a;
        }
        return a == 0 ? 0 : res;
}
复制代码

递归快速幂

递归快速幂的思想大致如下:

2^15可以分解成求2^7 * 2^7 * 22^7分解为2^3 * 2^3 * 2

2^3分解为2^1 * 2^1 * 2,当幂次为1的时候就是递归的边界条件,我们直接return。我们可以看到2^15依赖2^7的计算,2^7依赖2^3

所以我们可以用递归来实现它。

 private static double power2(int a,int b){
        if(b == 1){
            return a;
        }
        if((b & 1) == 1){
           return power2(a,b - 1) * a;
        }else {
            double temp =  power2(a,b / 2);
            return temp * temp;
        }
    }
复制代码

非递归快速幂

非递归快速幂的思想大致如下:

我们通过递归快速幂可以看到不断除以2,是不是跟右移动一位是一样的?所以我们换个角度,从n这个幂次的二进制出发,例如2^42,我们可以将42化成101010的二进制。

image.png

上述式子我们是很容易知晓的,指数相加。可以看到我们把101010拆成了1000001010,这是因为我们在计算的过程中,会先计算m * m == m^2=m^10(2),然后再计算(m * m) * (m * m) == m^4==m^100(2)m^8==m^1000(2)不就是对指数在移位吗?有多少位二进制就计算几次。

所以像计算2^100000(2)这种我们只需要移动100000(2)的长度6就行了,每次将上次的结果*结果得到新结果,幂次就会 * 2啦,我们知道* 2是会后面补0的,所以自然就慢慢得到100000,那么新结果这个时候就是我们2^100000(2),首先2^1 * 2^1 变成了2^10(2),然后2^2*2^2变成了2^100(2),最后通过5次这样的计算我们就可以算到2^100000(2)

回归正题,我们还有2^1010(2)没求呢?2^1010(2)分解成2^1000(2) * 2^10(2)2^1000(2)2^10(2)我们是不是在上述求2^100000(2)的过程中会有计算到2^1000(2)2^10(2)的步骤呢?所以我们只需要在计算到这的时候,记录一下当前的值就行了。根据我们的拆分规则,以为1为分界点的,101010(2)100000(2) + 1000(2) + 10(2)具体可以回归到上图观察下。每次我们遇到位数为1的时候就需要记录一下,以便于贡献给下次为1的时候计算。具体代码如下:

 private static double power3(int a,int b){
        double res = 1;
        double base = a;
        while(b > 0) {
            if ((b & 1) == 1) {
                res *= base;
            }
            base *= base;
            b >>= 1;
        }
        return res;
    }

复制代码

三种算法时间对比

public class Main {

    public static void main(String[] args) {
        long startTime = System.currentTimeMillis();
        System.out.println(power1(2,100000));
        System.out.println(System.currentTimeMillis() - startTime);

        startTime = System.currentTimeMillis();
        System.out.println(power2(2,100000));
        System.out.println(System.currentTimeMillis() - startTime);

        startTime = System.currentTimeMillis();
        System.out.println(power3(2,100000));
        System.out.println(System.currentTimeMillis() - startTime);

    }


    private static double power1(int a,int b){
        double res = 1;
        for (int i = 0; i < b ; i++){
            res *= a;
        }
        return a == 0 ? 0 : res;
    }


    private static double power2(int a,int b){
        if(b == 1){
            return a;
        }
        if((b & 1) == 1){
           return power2(a,b - 1) * a;
        }else {
            double temp =  power2(a,b / 2);
            return temp * temp;
        }
    }

    private static double power3(int a,int b){
        double res = 1;
        double base = a;
        while(b > 0) {
            if ((b & 1) == 1) {
                res *= base;
            }
            base *= base;
            b >>= 1;
        }
        return res;
    }

}
复制代码

image.png

PS:这里的Infinity表示超出范围了,这里我没有进行取模运算,只是为了看下算法的执行时间。

我们可以看到传统算法为11,而其他两种为0,在数据量特别大的时候,这种对比是特别明显的。传统的为O(n),快速幂为O(logn),所以为什么有这么大差别很明显了。

最后

感谢大家的阅读,另外快速幂在很多地方都是可以用到的,准确来说它就是一种思想,在计算幂时候的一种思想,它不仅仅用于求m的n次幂,还可以用来求矩阵的幂次矩阵快速幂),感兴趣的可以取了解一下。

最近一直在面试,总想着上岸再好好写文章,现在放平心态,每次学习都记录成文章或笔记,加油~ – ~。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享