快速幂

前言

学习这个算法看了这个视频,讲的很好!

问题

要想快速的求出\(a^n(n \in N^+)\)有什么办法呢?

前置知识

1. 按权展开

对于一个十进制数,我们可以将它拆分成若干个2的幂的和,这样就可以将它表现为二进制数,例如:
\(166 = 128 + 32 + 4 + 2 = 2^{7} + 2^5 + 2^2 + 2^1\)
那么它的二进制表示即为:
\((10100110)_2\)(注意二进制从右往左起是从2^0开始数)

2. 两个位运算符

按位与(&):

将两个数的二进制位进行比较,如果两个对应的位都是1则为1,否则为0
例如:
\(0 \& 0 = 0\)
\(1 \& 0 = 0\)
\(0 \& 1 = 0\)
\(1 \& 1 = 1\)
\(3 \& 5 = (011)_2 \& (101)_2 = (001)_2 = 1\)
那么如果将任意一个数&1,实际上是在判断它最右边的一位是不是1

右移(>>):

\(a >> b\)即将a的二进制位向右移动b位
例如:
\(5 >> 2 = (101)_2 >> 2 = 1\)
\(4 >> 1 = (100)_2 >> 1 = (10)_2 = 2\)
\(8 >> 1 = (1000)_2 >> 1 = (100)_2 = 4\)
\(7 >> 1 = (111)_2 >> 1 = (11)_2 = 3\)
所以如果任意一个数>>1实际上等价于这个数本身除以2,并且向下取整(其实就是将二进制位的最后一位去掉)

算法原理

首先来考虑\(n = 2^b, b \in N^+\)的情况
例如我们想要求出\(a ^ {128}\)次,可以发现:
\(a \times a = a^2\)
\(a^2 \times a^2 = a^4\)
\(a^4 \times a^4 = a^8\)
\(a^8 \times a^8 = a^{16}\)
\(a^{16} \times a^{16} = a^{32}\)
\(a^{32} \times a^{32} = a^{64}\)
\(a^{64} \times a^{64} = a^{128}\)
通过对a本身进行倍增,我们只用了七次就可以算出答案,也就是说,利用倍增的原理,我们可以用\(O(logn)\)的时间复杂度解决问题。
接下来推广到其他的情况:
我们知道,\(a^x \times a^y = a^{x + y}\), 所以对于任一的\(n, n \in N^+\),我们可以将它当成按权展开的形式,这样它就变成了多个2的不同的幂的和,我们分别求出这些情况的乘方,再将它们乘起来就可以了,例如:
\(105 = 64 + 32 + 8 + 1 = 2^6 + 2^5 + 2^3 + 2^0\)
我们可以按照开始的方法分别求出\(a^{64}, a^{32}, a^{8}, a^{1}\),然后将它们乘起来就是最后的答案了。
那么如何实现上述的想法呢?这里先给出代码再分析

代码

#define ll long long

ll quickPow(ll a, ll n) {
        int r = 1;
        while(n != 0) {
                if(n & 1 == 1) {
                        r *= a;
                }
                a *= a;
                n >>= 1;
        }

        return r;
}

在这个函数中,如果\(n\)不为零则进入循环体,首先会判断n的最后一位是否为1,我们设这一位是第\(k\)位,如果它为1,那么将n按权展开的话必然会有\(2 ^ k\)这一项,也就是说我们要求出\(a^{2^k}\)并将它乘入最终的结果中。此外,每次循环都会将a自乘,其实就是在自己倍增。每次执行一次最下面的\(n>>=1\)就使n的二进制位数减一,因此循环次数就是n的二进制位数(对于整数n来说,它的二进制位数为\(1+\lfloor\log_{2}{n}\rfloor\)),因此时间复杂度是\(O(logn)\)

posted @ 2023-12-09 13:56  dbywsc  阅读(10)  评论(0编辑  收藏  举报