深入理解非递归快速幂

Posted santiego

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入理解非递归快速幂相关的知识,希望对你有一定的参考价值。

深入理解非递归快速幂

网络上很多博客都只有板子,并没有严谨详细探讨其思想。本篇文章将从二进制角度来深入理解非递归快速幂

原理

先举个栗子,求 (3^{15}) 。对于 (3^{15}) 我们可以通过数学幂的运算法则得到下面的式子

[ 3^{17}=3^{2^{4}+2^{0}}=3^{2^{4}}*3^{2^{0}} ]

[ 3^{2^{i}}=3^{2^{i-1}}*3^{2^{i-1}} ]
我们会发现将其指数化为以2为底数的数之和后,计算 (3^{15}) 时,仅需算出 (3^{2^{4}})(3^{2^{0}}) 便可求得 (3^{15}) 的值,相比于遍历指数次,每次乘以底数的传统做法,效率更高。而我们又会发现 (3^{2^{i}}) 是由 (3^{2^{i-1}}) 转换过来的,所以,我们只需求出之和构成指数的2为底数的各个数(即问是哪些以2为底数的数累加起来为指数的),便可求解。
[ 17=2^{4}+2^{0} ]
而后,我们再通过观察发现,上面这个式子中的4和0其实就是17的二进制上不为0的二进制位

技术分享图片

(废话,二进制本身不就是将任意十进制数拆解为以2为底数的数之和的过程吗)

找出之和构成指数的以2为底数的各个数后,我们只需要从 (3^{2^{0}}) 开始,每次平方本身,推出第i位的 (3^{2^{i}}) ,并根据上面的分解规则(规律),逐位判断二进制从而解决最后的问题从而最终求出快读幂。

总而言之,我觉得快速幂算法其实是利用二进制将数分解为很多个递归平方(词穷了),每次都以平方级增长,从而实现快速求幂。

实现

先上个非递归快速幂板子

LL pow_mod(LL a, LL b, LL mo){
    LL ret = 1;
    while(b){
        if(b & 1) ret = (ret * a) % mo;
        a = (a * a) % mo;
        b >>= 1;
    }
    return ret;
}

首先需要明白的是,b&1在二进制中可以理解为取b的二进制最后一位,如果b&1返回1(即true)则末尾为1,反之为0(所以根据这个性质也可以很快地判断一个数是否为奇数),所以在这里只有当b的二进制最后一位为1时,才执行其后语句ret = (ret * a) % mo;;另外b >>= 1是表示将b右移1位,即将b的二进制整体向右移动一格。

技术分享图片

既然理解了原理以及实现要点,希望读者尽可能自己琢磨一下这个程序,可以模拟感受一下,因为那样来得更快

在这个程序中,每一次,无论当前二进制最后一位是否为1,a都要平方一下(因为 (3^{2^{i}}=3^{2^{i-1}}*3^{2^{i-1}}) ,前一个值会影响后一个值,所以必须维护),然后将当前二进制右移一位,去掉已经处理过的二进制数位以处理下一位,但当当前二进制最后一位为1时,根据之前的规律,需将a加入到答案中,最终当所有二进制数位都被处理掉后,b自然变为0,while循环结束。

以上。

参考




以上是关于深入理解非递归快速幂的主要内容,如果未能解决你的问题,请参考以下文章

学习记录:快速幂

学习记录:快速幂

快速幂

快速幂

带你深入理解 归并排序

数论—快速幂算法