目录
快速幂
快速幂
快速幂(Exponentiation by squaring,平方求幂)是一种简单而有效的小算法,它可以以的时间复杂度计算乘方。快速幂不仅本身非常常见,而且后续很多算法也都会用到快速幂。
递归快速幂
刚刚我们用到的,无非是一个二分的思路。我们很自然地可以得到一个递归方程:
计算a的n次方,如果n是偶数(不为0),那么就先计算a的n/2次方,然后平方;如果n是奇数,那么就先计算a的n-1次方,再乘上a;递归出口是a的0次方为1。
递归快速幂的思路非常自然,代码也很简单(直接把递归方程翻译成代码即可):
//递归快速幂
int qpow(int a, int n)
{
if (n == 0)
return 1;
else if (n % 2 == 1)
return qpow(a, n - 1) * a;
else
{
int temp = qpow(a, n / 2);
return temp * temp;
}
}
注意,这个temp变量是必要的,因为如果不把记录下来,直接写成qpow(a, n /2)*qpow(a, n /2),那会计算两次
,整个算法就退化为了
。
在实际问题中,题目常常会要求对一个大素数取模,这是因为计算结果可能会非常巨大,但是在这里考察高精度又没有必要。这时我们的快速幂也应当进行取模,此时应当注意,原则是步步取模,如果MOD较大,还应当开long long。
typedef long long ll;数据溢出时使用
数据多大要用long long呢?因为int是四个字节32位,表示的数的范围是-2147483648 ~ 2147483647[-2^31 ~ 2^31-1],2后面是有9位数的,也可以简单记忆为他可以表示10的9次方的数,再大一次方就不行了,有些时候你自己都不知道题目的数据超过了int的范围,当错误有时候不知道怎么解决,就是拿不了满分,那就该怀疑数据范围了,可以尝试把int换为 long long。
//递归快速幂(对大素数取模)
#define MOD 1000000007
typedef long long ll;
ll qpow(ll a, ll n)
{
if (n == 0)
return 1;
else if (n % 2 == 1)
return qpow(a, n - 1) * a % MOD;
else
{
ll temp = qpow(a, n / 2) % MOD;
return temp * temp % MOD;
}
}
有时候我们希望定义这样一种变量,它的值不能被改变,在整个作用域中都保持固定。为了满足这一要求,可以使用
const
关键字对变量加以限定.如 const int MOD=233333我们经常将 const 变量称为常量(Constant)。创建常量的格式通常为:
const type name = value;
const 和 type 都是用来修饰变量的,它们的位置可以互换,也就是将 type 放在 const 前面:
type const name = value;
但我们通常采用第一种方式,不采用第二种方式。另外建议将常量名的首字母大写,以提醒程序员这是个常量。
const用法详解:C语言const的用法详解,C语言常量定义详解 (biancheng.net)
大家知道,递归虽然简洁,但会产生额外的空间开销。我们可以把递归改写为循环,来避免对栈空间的大量占用,也就是非递归快速幂。
非递归快速幂
我们换一个角度来引入非递归的快速幂。还是7的10次方,但这次,我们把10写成二进制的形式,也就是 。
现在我们要计算 ,可以怎么做?我们很自然地想到可以把它拆分为
*
实际上,对于任意的整数,我们都可以把它拆成若干个
的形式相乘。而这些,恰好就是
、
、
……我们只需不断把底数平方就可以算出它们。
我们先看代码,再来仔细推敲这个过程:
//非递归快速幂
int qpow(int a, int n){
int ans = 1;
while(n){
if(n&1) //如果n的当前末位为1
ans *= a; //ans乘上当前的a
a *= a; //a自乘
n >>= 1; //n往右移一位
}
return ans;
}
最初ans为1,然后我们一位一位算:
1010的最后一位是0,所以a^1这一位不要。然后1010变为101,a变为a^2。
101的最后一位是1,所以a^2这一位是需要的,乘入ans。101变为10,a再自乘。
10的最后一位是0,跳过,右移,自乘。
然后1的最后一位是1,ans再乘上a^8。循环结束,返回结果。
这里的位运算符,>>是右移,表示把二进制数往右移一位,相当于/2;&是按位与,&1可以理解为取出二进制数的最后一位,相当于%2==1。这么一等价,是不是看出了递归和非递归的快速幂的关系了?虽然非递归快速幂因为牵扯到二进制理解起来稍微复杂一点,但基本思路其实和递归快速幂没有太大的出入。
关于快速幂 修改自:算法学习笔记(4):快速幂 - 知乎 (zhihu.com)