快速幂
1.定义
顾名思义,快速幂就是快速算底数的n次幂。其时间复杂度为 O(log₂N), 与朴素的O(N)相比效率有了极大的提高。
快速幂算法的核心思想就是每一步都把指数分成两半,而相应的底数做平方运算。这样不仅能把非常大的指数给不断变小,所需要执行的循环次数也变小,而最后表示的结果却一直不会变。
2.应用【用来提高运算速度】
写这篇博客之前,
在网上看见一篇写的很好的文章,
真正的重剑无锋吧。
我更想以我自己的方式去记下来
原链接
老规矩。
先举个例子:ZZULIOJ 1090
现在有两个数 a和b,
而我要求a的b次方也就是a^b的最后三位数。
最简单的方法就是循环b次。
int a,b,i,result=1;
for(i=0;i<b;i++)
{
result=result*a;
}
result=result%1000;
时间复杂度为O(n),如果b特别大,那么循环时间会特别长,
重点是,还会超出int的范围,而如果再大,还会超出long long的范围。
这个时候,结果就会和预期不一样。
现在去思索求这个后三位,是不是有其他的办法。
关于取模(也即是数学中的取余运算)
利用数学公式可以推出这样一个式子。
(a * b) % p = (a % p * b % p) % p
这样的话,结果的量会小很多,完全不用担心超出范围了。
根据这个取模公式,我们可以优化下我们的数字范围。
或者可以这么理解,后三位的结果,只会与后三位有关。
那么优化后的代码,就变成了这样:
int a,b,i,result=1;
for(i=0;i<b;i++)
{
result=result*a;
result=result%1000;
}
result=result%1000;
这样每次提前取模,将数字的范围缩小了,
但是运算时间却没有进行优化,
有没有一种方法能够优化循环次数呢?
下面我就来阐述下快速幂算法的思路。
2^10就是10个2连续相乘
4^5就是5个4连续相乘
两个结果是一样的
但是如果是利用循环,循环次数却缩减了一半
这里我们不难想到
如果每一步都进行如此优化
那么
2^10//需要循环十次
=4^5//把2的平方计算
=4*8^2//计算4的平方
=4*64//计算8的平方
每个等号后面都比前面少循环了一半
这就是快速幂算法的思路
快速幂算法到底干了什么?
你会发现,
原本时间复杂度为O(n)的循环
在一次次平方平方下
变成了时间复杂度为O(log₂n)
以此类推
这样就可以省去大量时间,优化了代码
如果现在要计算2^1024,
只需要把2^1024通过一次次运算,
最终变成10次循环计算出答案,
这样就可以省去大部分时间。
代码如何来实现快速幂的操作呢?
int fastPower(int base, int power)//定义快速幂函数
{
int result = 1;
/*
这里我要说下原来的原作者代码我认为的小纰漏
原题目底数和幂指数范围都在10000之间
那么如果是直接平方运算也不会超过int的表示类型
作者的i用int 但是power用long long
虽然实际上power出现超过int的概率很小
或者说作者应该将int i改成long long i
所以我认为这算是一点小问题吧
而且就此问题规模来言,int就足够了
然后如果我表述或者理解有问题,也希望大家在评论区帮忙斧正
*/
while (power > 0)
{if (power % 2 == 0)//如果指数为偶数
{ power = power / 2;//把指数缩小为一半
base = base * base % 1000;} //底数变为底数的平方
else //如果指数为奇数
{ power = power - 1;//把指数减去1,使其变成一个偶数
result = result * base % 1000;//这里要把去掉的指数1给乘进来
power = power / 2;//此时指数为偶数,可以继续执行操作
base = base * base % 1000;}}//底数变为底数的平方
return result;//计算结果返回result
}
到这里快速幂算法的代码也算是展示完了。
但是观察代码我们不难发现,
出现了类似的的语句
而且通过对于整型运算我们知道
5/2的结果是2,4/2的结果也是2
那么else语句中的
power=power-1
power=power/2
可以直接变为
power=power/2
那么我们的代码就出现了重复的部分
唯一的区别就是是否乘进来原来的底数一次
那么现在我们的代码就可以优化为这样
int fastPower(int base, int power)//定义快速幂函数
{
int result=1;
while (power > 0)
{if (power % 2 == 1)//如果指数为奇数
{ result = result * base % 1000;} //将减去的指数1给乘进来
power = power / 2;//指数/2,优化计算量
base = base * base % 1000;}}//底数变为底数的平方
return result;//计算结果返回result
}
到这里我们的代码优化的效果已经和普通方法的速度可以说是天壤之别了
但是,我们还能再次优化代码,让运算速度更快
这里运用的是位运算的法则
感兴趣可以自己再看一下。
那么我就选和本题有关的
在2进制中,如果数字是奇数
那么最后一位肯定是1
比如7的二进制是111
13的二进制是1101
再说偶数的末尾肯定是0
比如2的二进制10
14的二进制1110
这和进制的计算方法有关
末尾是2的0次幂
那么它表示就是1或者0
所以末尾的数字
决定了2进制数字的奇偶
那么我们就可以运用更快的位运算
去替代我们的power%2==1
语句
我们将它改成power&1
奇数时结果为1
偶数时结果为0
相应的power=power/2
语句
也可以通过位运算
进行移位操作,用到移位运算符
右移运算>>来实现
那么就可以改为power >>= 1
将程序运行速度显著提升。
那么改写后的代码就是
int fastPower(int base, int power)
{
int result = 1;
while (power > 0)
{
if (power & 1)
{ result = result * base % 1000;}
power >>= 1;//此处等价于power=power/2
base = (base * base) % 1000;
}
return result;
}
3.效果及例题
链接:http://acm.hdu.edu.cn/showproblem.php?pid=2035
来源:hdu
题目描述
求A^B的最后三位数表示的整数。
说明:A^B的含义是“A的B次方”
输入
输入数据包含多个测试实例,每个实例占一行,由两个正整数A和B组成(1<=A,B<=10000),如果A=0, B=0,则表示输入数据的结束,不做处理。
输出
对于每个测试实例,请输出A^B的最后三位表示的整数,每个输出占一行。
样例输入
2 3
12 6
6789 10000
0 0
样例输出
8
984
1
我的代码
这次直接用C++代码写了,先来个普通幂运算,可以AC
#include<bits/stdc++.h>
using namespace std;
int normalPower(int base, int power)
{ int result = 1;
for (int i = 1; i <= power; i++)
{
result = result * base;
result = result % 1000;
}
return result % 1000;
}
int main()
{
int base, power;
while (true)
{
cin >> base >> power;
if (base == 0 && power == 0) break;
cout << normalPower(base, power) << endl;
}
return 0;
}
下面我用快速幂写一下。
#include<bits/stdc++.h>
using namespace std;
int fastPower(int base, int power)
{
int result = 1;
while (power > 0)
{
if (power & 1)
{ result = result * base % 1000;}
power >>= 1;
base = (base * base) % 1000;
}
return result%1000;
}
int main() {
int base, power;
while (true) {
cin >> base >> power;
if (base == 0 && power == 0) break;
cout << fastPower(base, power) << endl;
}
return 0;
}
小结
快速幂整体来说并不难,综合性比较强。
然后为了一次次优化参考了不少大佬的博客。
自己也学到了很多新知识。感谢各位奉献者们。