博客园  :: 首页  :: 管理

而 pow(g, a, p) 之所以能“秒算”,是因为它在底层使用了高度优化的快速幂算法结合模运算(Exponentiation by Squaring with Modular Reduction)。

它的精妙之处在于“边算边约减”——在每一次平方或乘法运算后,立刻对中间结果进行取模操作。这样,整个计算过程中的所有中间变量都被死死地控制在

p 的范围之内,彻底避免了产生巨大中间值的问题。

这里举例说明 pow(3, 13, 7) ,即表示 计算 3^13 % 7 的结果 , 其核心是要将 3^13 分解成多个数的乘积 , 计算机中,最快最简单的就是3不变, 将指数13进行分解

如果按照你直觉中的“先算出完整结果再取模”,我们需要先算 3^13 ,这等于 1594323,然后再用 1594323 除以 7 求余数。如果指数更大,这个数字会大到计算机直接爆内存。

而快速幂算法的“边算边约减”和“二进制分解”是这样操作的: 

首先,我们将指数 13 写成二进制:1101  ,  在二进制中,每一位代表一个权重:

0 位是 1 - 代表 2^0 = 11 位是 0 - 代表 2^1 = 22 位是 1 - 代表 2^2 = 43 位是 1 - 代表 2^3 = 8

所以,13=8+4+1,而且可以观察出来,二进制中,低位的1如果为x ,它旁边高位的1就代表是x的2倍,即2x了

这样,我们带入底数3 , 也即如果 3^x 的下一个高位如果是1 , 就是 3^(2x) ,  也即是 (3^x)^2 ,  也即低高位都是1 , 位数的3^x 的 平方 就是下一个高位的结果

那么数学上,3^13 就可以拆解为:3^8 * 3^4 * 3^1 = 3^(8+4+1)

第二步:开始迭代计算(核心过程)

我们准备两个变量:结果(res) = 1,底数(base) = 3。我们从二进制的最低位(最右边)开始往左看:

 

【第 1 轮】处理二进制的最后一位(1)

  • 判断:当前位是 1,说明这个  3^1  需要被乘进最终结果里。
  • 更新结果:res = res × base % 7 → 1 × 3 % 7 = 3。
  • 底数自乘取模:不管这一位是不是1,底数都要平方,为下一位做准备。base = 3 × 3 % 7 = 9 % 7 = 2。(注意!这里立刻取了模,把 9 变成了 2,数值非常小)。【这也代表下一个高位3^高位1位置的结果 % 7  = 2】
  • 移动:向右移一位,处理下一个二进制位。

 

【第 2 轮】处理二进制的倒数第二位(0)

  • 判断:当前位是 0,说明不需要乘入结果,跳过。
  • 底数自乘:base = 2 × 2 % 7 = 4。(同样立刻取模,保持数值很小)。
  • 移动:继续右移。

 

【第 3 轮】处理二进制的倒数第三位(1)
  • 判断:当前位是 1,需要乘入结果。此时的 base 已经通过连续平方代表了 3^4 。
  • 更新结果res = res × base % 73 × 4 % 7 = 12 % 7 = 5
  • 底数自乘base = 4 × 4 % 7 = 16 % 7 = 2。(再次取模)。
  • 移动:继续右移。

 

【第 4 轮】处理二进制的最高位(1)
  • 判断:当前位是 1,需要乘入结果。此时的 base 代表了 3^8 。
  • 更新结果res = res × base % 75 × 2 % 7 = 10 % 7 = 3
  • 底数自乘base = 2 × 2 % 7 = 4
  • 移动:右移后没有更多位数了,结束。

 

所以最后的结果就是3 ,  这里我们总结一下:

我们可以看到,整个过程中,我们从来没有计算过像 1594323 这样的大数。每一次乘法之后,我们都立刻 % 7 

这就是为什么 Python 的 pow(g, a, p) 能够“秒算”:它利用二进制把巨大的指数拆解成了少量的平方和乘法操作

时间复杂度从 O(a)降到了 O(log a) 并且死死地把所有中间数字控制在模数 p 的范围内,彻底避免了内存爆炸。

 

 

尊重别人的劳动成果 转载请务必注明出处:https://www.cnblogs.com/5201351/p/20331011