蒟蒻写文,难免疏忽,欢迎来踩!
快速幂的必要性
在讲快速幂之前,有必要阐明快速幂的重要性。
快速幂之所以重要,是因为很多数论的题目都需要快速的求出某数的幂。在这种情况下,朴素算法的O(n)时间复杂度难以满足要求,故考虑使用快速幂。
快速幂的思路与实现(PRE)
首先我们来熟悉两种位运算以及与之等价的十进制运算。
第一种是向右位移,操作符是>>,操作符的左值是要进行位移的数,右值是位移位数。为了使笔记更加简洁,我们在此只介绍>>1。>>1是指将被操作数转换成二进制后将数整体向右移动一位,以(10000101001)
2=(1065)
10为例,10000101001>>1=(1000010100)
2=(532)
10。我们可以发现右移一位等价于十进制中的除以2然后下取整。因此我们能得出公式A
(2)>>1=[B
(10)/2]。其中A
(2)=B
(10)。对于此结论的证明,我们可以将10进制数字转换成2
n的和的形式,然后模拟位移的计算过程。
然后我们来介绍第二种,它是按位和,操作符&,操作符左右值皆为被操作数。由于算法中我们只需要&1,因此只介绍&1 。按位和要求将二进制位数较少的数用0补齐。例如我们在计算1065&1的时候实际上我们是在计算(10000101001)
2&(00000000001)
2。这样,我们很容易看出来,按位与1的十进制实质是判断奇偶。如果左值是偶数,那么运算结果为0;如果为奇数则为1 。
介绍这两种位运算的主要目的是节约快速幂的运算时间。当然这些位运算也可以通过正常的操作来实现,但是位运算在速度上略胜一筹。
快速幂的思路与实现
刚才在介绍右位移操作时我们曾经提到任何一个十进制整数都能转换成2 n的和的形式。现在假设我们被要求求出a b。那么我们可以将b分解为之前2的幂的和的形式,并使用右移一位的形式逐位判断b的各个位。在判断之后我们还需要对相应的结果进行操作,因此我们要维护一个变量,使之符合当前正在运算的数位,一旦判断b当前的数位为1,则答案立即乘以维护好的变量。
给出上述思路的C++代码:
/* import guide: the macro NUM should be defined as a data type e.g. long long */ NUM quickPow(NUM a,NUM b){ NUM ans=1,curr=a; while(b!=0){ if(b&1!=0) ans*=curr; curr*=curr; b>>=1; } return ans; }
请注意快速幂本身并不处理上溢的错误,一定要按照使用时的要求合理定义变量类型!
快速幂的复杂度
快速幂算法的时间复杂度是O(log n),因为快速幂只需要b的二进制位数次操作。
快速幂算法的空间复杂度是O(1),并不涉及到空间上的规模问题,因此是常数空间。