快速幂

快速幂

一般的求幂方法#

初学编程时,学校一般都会出求x的n次幂的题目。而在做基础题的时候,也经常会用到math.h中的pow函数来求x的n次幂。但是,这些方法不一定能满足我们开发中所需效率。

//迭代法
int pow(int x, int n){
    int ans = 1;
    while(n--)
        ans *= x;
    return ans;
}
//递归法
int pow(int x, int n){
    //base case
    if(!n)	return x;
    return x*pow(x, n-1);
}

这种方法是初学时常用的解法,日常使用也是足够的,时间复杂度为O(N),而递归法则要用到O(N)的空间复杂度。

但有时,我们的底数和指数中有小数或指数有负数时,这种方法往往还需要再写多几行判断和扩展。那么我们就会用pow来求,往往就会用到精度更高的double类型变量,内部的实现远比上述复杂,所以仅仅在整数间运算,我们没必要用pow函数。

快速幂算法#

整数间的求幂可以用快速幂算法来把时间复杂度降到O(logN),快速幂算法不仅常见,后续很多算法也会用到。

接下来我们讨论这个算法的原理。

xn=xxx...xn

n:xn=xxn1

n:xn=xn2xn2

n0:xn=1

这是一个二分的分治思路,我们可以不断地把偶数情况一直拆解成相似的小问题,那么递归方程也显而易见了

xn={xxn1,whennisoddxn2xn2,whenniseven1,n=0

//递归方法实现
//time complexity:O(logN)
//space comlexity:O(logN)
int quickPow(int x, int n){
    if(!n)	return 1;
    else if(n & 1)	return quickPow(a, n-1)*a; //此处位运算为判断奇数
    else{
        int temp = quickPow(a, n/2);
        //这里必须用一个变量储存递归结果,如果在return里面调用两次递归,就不是分治了,时间复杂度还是线性阶
        return temp*temp;
    }
}

实际问题中,如果计算结果特别大,我们会对一个大素数进行取模,这时快速幂算法也需要取模,而且是步步取模。我们的素数较大,可能需要用到long long型变量来运算。

#define MOD 1000000007
long long quickPow(long long x, long long n){
    if(!n)	return 1;
    else if(n & 1)	return quickPow(x, n-1) % MOD;
    else{
        long long temp = quickPow(a, n/2) % MOD;
        return temp*temp;
    }
}

递归的代码简洁,又能非常贴切的描述上面推导的递归方程,效率也能比一般求幂方法高,但是众所周知递归是会占用额外的程序栈空间的,所以如果我们能把这个过程换成迭代来实现,效果会更好。

快速幂的迭代方法#

迭代方法中,我们就需要换一个角度去思考这种分治的思想。其实在上面的代码中,我们用了一个temp变量来储存了当前层递归的结果来达到分治,那么在迭代中,如果也能不停地调用前面的计算结果,减少运算次数,也就提高了效率。

我们知道,数值数据在计算机中以二进制储存,我们可以在推导中把指数看作二进制形式,我们以x的7次方为例

x7=x(0111)2=x(0100+0010+0001)2

x7=x(100)2x(10)2x(1)2=x4x2x

可以看出,我们只需要不断地把当前底数乘方,再让幂除二就可以实现快速幂算法了。当然,奇数情况下要先乘一个x。

int quickPow(int x, int n){
    int ans = 1;
    while(n){
        if(n & 1)
            ans *= x;
        x *= x;
        n >= 1; //右移一位,相当于除2
    }
    return ans;
}

虽然上述实现为整数,但其实只要x的数据类型支持乘法且满足结合律,快速幂的算法都是有效的。矩阵,高精度整数都可以照搬这个思路。

拓展:为什么要对大素数“1000000007”取模#

有时候,我们的算法得出结果会超过32位整型的范围,题目为了精度或是防止整数溢出,就需要对一个大数字进行取模。

那么为什么是1000000007呢?

因为这个数字是10位的最小质数,跟质数取模能最大程度避免冲突

而对于int32位的最值,1000000007这个数字足够大,它的平方在int64的范围中也不会溢出。

参考文章#

"算法笔记(4): 快速幂"
"为什么要对1000000007取模"

posted @   骆驼弟弟  阅读(69)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示
主题色彩