UP主五点七边《快速幂都能做什么?小小的算法也有大大的梦想》(BV16Z4y1M7y1)这期视频做的太好了,看完我总算是理解快速幂是什么意思了,为了巩固知识点自己码了一遍学习笔记:
【声明:本文中统一用n代指底数,p代指指数,文中代码有一些是伪代码】
(一)引入
如果我们要计算n^64,最一般的朴素算法是:
repeat(64-1) //repeat指重复执行的次数
{
sum+=sum*n;
}
显然这样的时间复杂度非常之高。为了优化算法,我们使用快速幂算法:
快速幂算法的核心思想是:n^p = n^(p1 + p2 + ... + pk)= n^p1 * n^p2 *...* n^pk,其中p1、p2、...、pk都是2的整数次方,也就是:把幂分解成2的幂的和。
举个例子:
求n^64,我们就把它分解为求n^(1+1+2+4+8+16+32),
求n^105,我们就把它分解为求n^(1+8+32+64)。
(二)求“分解幂”
那接下来的问题便转化成了:如何把一个数分解成2的幂的和,在这里我们把这些 “2的幂” 叫作 “分解幂”。我们以n^105为例子,首先分别写出105、1、8、32、64的二进制形式:
105 1 1 0 1 0 0 1
—————————
1 0 0 0 0 0 0 1
8 0 0 0 1 0 0 0
32 0 1 0 0 0 0 0
64 1 0 0 0 0 0 0
通过观察我们发现,105的二进制数有4个1,其他四个数的二进制数各有一个1,且它们处在的位置和105一一对应。
我们再对这四个1的位置进行进一步观察:
位 7 6 5 4 3 2 1
—————————
105 1 1 0 1 0 0 1
—————————
1 0 0 0 0 0 0 1
8 0 0 0 1 0 0 0
32 0 1 0 0 0 0 0
64 1 0 0 0 0 0 0
可以发现,105的四个1分别处在第1、第4、第6、第7位上,其余位都是0。
到这里,我们就能大概摸出快速幂算法的工作方式了:
步1:将105转化为二进制数;
步2:从二进制数的第1位开始往高位遍历,遇到1就确定这个位对应的“分解幂”,遇到0就跳过不管;
步3:经过了前两个步骤,已经将n^105成功转化为若干个“n^分解幂”的积。
(三)代码实现
那么我们怎么用代码来实现上面的步骤呢?
1、获取底数n和指数p。不用多说:
get(n);
get(p);
2、遍历指数p的二进制数的每一位。C/C++使用位运算符“ >> ”,意思是将其二进制数向右移一位,也就是达到了第1位向高位遍历的效果:
p>>=1;
拿一个二进制数直观地演示一下,[ ]代表当前遍历到的数:
0 1 0 1 [0] 跳过
p>>=1;
0 0 1 0 [1] 取分解幂
p>>=1;
0 0 0 1 [0] 跳过
p>>=1;
0 0 0 0 [1] 取分解幂
p>>=1;
0 0 0 0 [0] 跳过
3、判断遍历到的该位是否为1。使用位运算符“ & ”:
p&1 = 1 :即该位上是1
p&1 = 0 : 即该位上是0
4、判断遍历结束。由于我们是使用移位运算符,所以当p的所有1都被遍历完之后,p也就变成0,此时遍历即可结束:
0 0 0 0 [0] 跳过
遍历结束;
5、综上所述,用C写出的快速幂函数代码如下:
long long fastpower(long long n,long long p)
{
long long ans=1;
while(p)
{
if(p&1)
{
ans=ans*n;
}
n=n*n;
p>>=1;
}
return ans;
}
(四)结论
如果分别按朴素算法和快速幂算法来求2^60的话,可以发现朴素算法需要的运算次数为60,而
快速幂算法的运算次数为6。所以,快速幂算法的意义在于将求某数n次方的时间复杂度从O(p)
降为了O(log₂p)!(log₂60 ≈ 6)
感谢我的好同学帮我一起完善本博客,如有错漏还敬请指出!快速幂算法的具体应用篇幅还是比较长的,找时间再单独写一篇笔记吧。大家也可以关注下up主五点七边,他的动画很简洁明了。