zoukankan      html  css  js  c++  java
  • 深入理解非递归快速幂

    深入理解非递归快速幂

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

    原理

    先举个栗子,求 (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循环结束。

    以上。

    参考

    本文采用 知识共享 署名-非商业性使用-相同方式共享 3.0 中国大陆 许可协议进行许可。欢迎转载,请注明出处: 转载自:Santiego的博客

  • 相关阅读:
    JavaScript对原始数据类型的拆装箱操作
    Javascript继承(原始写法,非es6 class)
    动态作用域与词法作用域
    自行车的保养
    探索JS引擎工作原理 (转)
    C语言提高 (7) 第七天 回调函数 预处理函数DEBUG 动态链接库
    C语言提高 (6) 第六天 文件(续) 链表的操作
    C语言提高 (5) 第五天 结构体,结构体对齐 文件
    C语言提高 (4) 第四天 数组与数组作为参数时的数组指针
    C语言提高 (3) 第三天 二级指针的三种模型 栈上指针数组、栈上二维数组、堆上开辟空间
  • 原文地址:https://www.cnblogs.com/santiego/p/9521181.html
Copyright © 2011-2022 走看看