try ai
科普
编辑
分享
反馈
  • 平方求幂

平方求幂

SciencePedia玻尔百科
核心要点
  • 平方求幂算法极大地减少了计算大数幂所需的乘法次数,实现了对数时间复杂度,而非线性时间复杂度。
  • 该算法通过将指数分解为其二进制表示,并执行一系列的平方和乘法操作来工作。
  • 它是现代密码学中的一个基础算法,为RSA、素性测试和椭圆曲线密码学等系统提供了高效的模幂运算能力。
  • 该原理可以从数推广到任何具有结合律的运算,例如用于求解线性递推关系的矩阵乘法或分析图中的路径。

引言

计算一个数的巨大次幂,例如 210242^{1024}21024,似乎是一项需要巨大计算量的任务。直接的重复相乘方法速度慢得令人望而却步,使得许多理论应用变得不切实际。这就带来了一个重要的知识缺口:我们如何才能高效地执行如此大规模的计算?答案在于一种优雅而强大的算法,称为平方求幂(exponentiation by squaring),它是现代计算机科学和应用数学的基石。这种方法将一个不可能完成的漫长线性过程转变为一个非常快速的对数过程。

在本文中,我们将深入探讨这一核心算法。我们将首先深入“原理与机制”,揭示指数的二进制表示如何实现这种显著的加速,并考察其实现的分步逻辑。接下来,在“应用与跨学科联系”部分,我们将考察其广泛的影响,揭示这一简单思想如何成为现代密码学、物理系统模拟和复杂网络分析背后的引擎。

原理与机制

想象一下,有人要你计算 3643^{64}364。这或许是个艰巨的任务?你可以耐心地将3自乘63次。这是暴力方法,一条漫长而曲折的道路。它可行,但效率极低。现在,如果我告诉你有一条绝妙的捷径,一条能在短短六步内让你得到答案的秘密通道呢?

这不仅仅是派对上的小把戏;它是一项深刻的算法原理,称为​​平方求幂​​(exponentiation by squaring),有时也叫作二进制求幂(binary exponentiation)。它是现代计算的基石,尤其是在密码学领域,其优雅之处在于一个关于数之本质的简单而强大的观察。

2的幂的力量

与其重复乘以3,不如尝试一种不同的策略:不断对数字进行平方。

  • 31=33^1 = 331=3
  • 32=(31)2=3×3=93^2 = (3^1)^2 = 3 \times 3 = 932=(31)2=3×3=9
  • 34=(32)2=9×9=813^4 = (3^2)^2 = 9 \times 9 = 8134=(32)2=9×9=81
  • 38=(34)2=81×81=65613^8 = (3^4)^2 = 81 \times 81 = 656138=(34)2=81×81=6561
  • 316=(38)2=65612=43,046,7213^{16} = (3^8)^2 = 6561^2 = 43,046,721316=(38)2=65612=43,046,721
  • 332=(316)2=…3^{32} = (3^{16})^2 = \dots332=(316)2=…
  • 364=(332)2=…3^{64} = (3^{32})^2 = \dots364=(332)2=…

看!我们仅用六次平方运算就得到了3的64次幂。这比朴素方法所需的63次乘法要快上指数倍。这种重复平方是该算法的核心。但如果指数不是一个恰好的2的幂呢?比如说,我们该如何计算 7697^{69}769?

秘密在于计算机的语言:二进制。任何整数都可以唯一地表示为2的幂之和。这就是它的​​二进制表示​​。对于数字69,我们可以将其写为:

69=64+4+1=1⋅26+0⋅25+0⋅24+0⋅23+1⋅22+0⋅21+1⋅2069 = 64 + 4 + 1 = 1 \cdot 2^6 + 0 \cdot 2^5 + 0 \cdot 2^4 + 0 \cdot 2^3 + 1 \cdot 2^2 + 0 \cdot 2^1 + 1 \cdot 2^069=64+4+1=1⋅26+0⋅25+0⋅24+0⋅23+1⋅22+0⋅21+1⋅20

用二进制,这写作 100010121000101_210001012​。利用指数法则 xa+b=xa⋅xbx^{a+b} = x^a \cdot x^bxa+b=xa⋅xb,我们可以分解我们的问题:

769=7(64+4+1)=764⋅74⋅717^{69} = 7^{(64+4+1)} = 7^{64} \cdot 7^4 \cdot 7^1769=7(64+4+1)=764⋅74⋅71

突然间,问题解决了!我们只需要通过重复平方预先计算出“2的幂”次项(71,72,74,78,…7^1, 7^2, 7^4, 7^8, \dots71,72,74,78,…),然后只将那些对应指数二进制表示中'1'的项相乘即可。这个简单的分解就是该算法的全部概念基础。

算法实战

我们可以将这一洞见形式化为一个分步过程。有几种流行的方法可以做到这一点,但它们都遵循相同的二进制节奏。

“从左到右”法

想象一下从左到右(从最高有效位到最低有效位)扫描指数的二进制位。让我们计算 1712317^{123}17123。指数123的二进制是 111101121111011_211110112​。算法流程如下:

  1. 从结果等于底数17开始(对应开头的'1'位)。
  2. 移至下一位。总是将当前结果平方。
  3. 如果这个新位是'1',则将结果乘以原始底数(17)。
  4. 对所有剩余的位重复此过程。

让我们对 123=(1111011)2123 = (1111011)_2123=(1111011)2​ 进行追踪:

  • ​​第1位 (1):​​ 结果是 171717。
  • ​​第2位 (1):​​ 平方:17217^2172。该位是1,所以乘以底数:(172)⋅17=173(17^2) \cdot 17 = 17^3(172)⋅17=173。
  • ​​第3位 (1):​​ 平方:(173)2=176(17^3)^2 = 17^6(173)2=176。该位是1,所以乘以底数:176⋅17=17717^6 \cdot 17 = 17^7176⋅17=177。
  • ​​第4位 (1):​​ 平方:(177)2=1714(17^7)^2 = 17^{14}(177)2=1714。该位是1,所以乘以底数:1714⋅17=171517^{14} \cdot 17 = 17^{15}1714⋅17=1715。
  • ​​第5位 (0):​​ 平方:(1715)2=1730(17^{15})^2 = 17^{30}(1715)2=1730。该位是0,所以不做额外操作。
  • ​​第6位 (1):​​ 平方:(1730)2=1760(17^{30})^2 = 17^{60}(1730)2=1760。该位是1,所以乘以底数:1760⋅17=176117^{60} \cdot 17 = 17^{61}1760⋅17=1761。
  • ​​第7位 (1):​​ 平方:(1761)2=17122(17^{61})^2 = 17^{122}(1761)2=17122。该位是1,所以乘以底数:17122⋅17=1712317^{122} \cdot 17 = 17^{123}17122⋅17=17123。

我们得到了正确答案。请注意操作次数:除第一位外,我们为每个位执行了一次平方运算,并为每个'1'位执行了一次乘法运算。对于有7位的123((1111011)2(1111011)_2(1111011)2​),这总计是6次平方和5次乘法。

“从右到左”法与递归

另一种看待该算法的优雅方式是从右到左,这恰好反映了我们最初推导的数学公式。让我们计算 3213^{21}321。指数21是 10101210101_2101012​。这种方法可以用递归优美地表达。

想象一个函数,它维持一个不变量:final_result = accumulator * (base^exponent)。我们的目标是将 exponent 削减到0,此时 accumulator 将持有最终答案。

  • ​​初始调用:​​power(base=3, exponent=21, accumulator=1)。不变量成立:321=1⋅3213^{21} = 1 \cdot 3^{21}321=1⋅321。
  • ​​第1步:​​ 指数21是奇数。这对应于最右边的'1'位。我们将一个底数次幂“剥离”到累加器中。调用变为 power(base=3*3, exponent=10, accumulator=1*3)。不变量得以保持:1⋅321=3⋅(32)10=3⋅3201 \cdot 3^{21} = 3 \cdot (3^2)^{10} = 3 \cdot 3^{20}1⋅321=3⋅(32)10=3⋅320。
  • ​​第2步:​​ 指数10是偶数。这对应于下一位'0'。我们不改变累加器。我们只将底数平方并将指数减半:power(base=(3^2)^2, exponent=5, accumulator=3)。不变量得以保持:3⋅(32)10=3⋅(34)53 \cdot (3^2)^{10} = 3 \cdot (3^4)^53⋅(32)10=3⋅(34)5。
  • ​​第3步:​​ 指数5是奇数。剥离一个:power(base=(3^4)^2, exponent=2, accumulator=3*(3^4))。
  • 依此类推。

在每一步中,我们要么只平方底数(如果指数位是0),要么将底数乘入累加器并平方底数(如果指数位是1)。这正是像 这样的问题中推导出的S(平方)和M(乘法)操作的模式。这种带有​​累加器​​的递归观点是计算机科学中一个强大的概念,通过其保持的不变量保证了算法的正确性。

对数飞跃:为何速度至关重要

平方求幂的真正胜利在于其效率。对于一个指数 eee,朴素方法大约需要 eee 步。而平方求幂所需的步数与 eee 的二进制位数成正比,大约是 log⁡2(e)\log_2(e)log2​(e)。

这是从​​线性时间​​到​​对数时间​​的飞跃。差异是惊人的。要计算一个有300位数字的指数,朴素方法所需的时间比宇宙的年龄还要长。而平方求幂大约只需1000步就能完成,在现代计算机上只需一瞬间。这与使得在有序列表中搜索(二分搜索)比逐个检查快得多的算法魔法是相同的。当我们正式分析该算法时,总的位操作数取决于指数的位数 LLL 和我们相乘的数字的位数 kkk。这给出了 O(L⋅k2)\mathcal{O}(L \cdot k^2)O(L⋅k2) 的复杂度,这是一个非常高效的结果。

密码学领域:驯服巨数

这个算法不仅仅是学术上的好奇心;它是现代公钥密码学(如RSA算法)背后的主力。这些系统依赖于一种称为​​模幂运算​​的操作:计算 ae(modn)a^e \pmod nae(modn)。

在这里,我们不关心 aea^eae 本身的巨大数值,只关心它除以 nnn 后的余数。其美妙之处在于,平方求幂在​​模算术​​的世界中完美适用。因为 (x⋅y)(modn)=((x(modn))⋅(y(modn)))(modn)(x \cdot y) \pmod n = ((x \pmod n) \cdot (y \pmod n)) \pmod n(x⋅y)(modn)=((x(modn))⋅(y(modn)))(modn),我们可以在算法的每一步都取模。每一次平方和每一次乘法之后都立即进行模 nnn 的约简。这使得我们处理的所有数字都保持在小而可控的范围内,无论指数多么巨大,都能防止它们溢出计算机的内存。

模算术的世界甚至提供了更多的捷径。

  • ​​用欧拉定理缩小指数:​​ 如果指数 eee 本身大得惊人怎么办?伟大数学家 Leonhard Euler 的一个精彩成果前来解救。​​欧拉函数定理​​指出,如果 gcd⁡(a,n)=1\gcd(a, n) = 1gcd(a,n)=1,那么 aφ(n)≡1(modn)a^{\varphi(n)} \equiv 1 \pmod naφ(n)≡1(modn),其中 φ(n)\varphi(n)φ(n) 是一个与 nnn 相关的特殊数字(小于 nnn 且与 nnn 互质的整数个数)。这意味着 aaa 的幂以一个长度为 φ(n)\varphi(n)φ(n) 的因数的周期重复。因此,我们可以在开始计算之前就将指数 eee 对 φ(n)\varphi(n)φ(n) 取模!为了计算 72025(mod1000)7^{2025} \pmod{1000}72025(mod1000),我们首先找到 φ(1000)=400\varphi(1000) = 400φ(1000)=400。然后我们看到 2025≡25(mod400)2025 \equiv 25 \pmod{400}2025≡25(mod400)。这个庞大的问题被简化为仅仅计算 725(mod1000)7^{25} \pmod{1000}725(mod1000),一个容易得多的任务。

  • ​​用逆元进行回溯:​​ 那么负指数呢,比如 17−123(mod101)17^{-123} \pmod{101}17−123(mod101)?在模算术中,没有除法。取而代之的是乘以一个​​模逆元​​。我们可以找到一个整数 xxx 使得 17x≡1(mod101)17x \equiv 1 \pmod{101}17x≡1(mod101);这个 xxx 就是逆元,17−117^{-1}17−1。使用​​扩展欧几里得算法​​,我们可以找到 17−1≡6(mod101)17^{-1} \equiv 6 \pmod{101}17−1≡6(mod101)。我们的问题就变成了计算 6123(mod101)6^{123} \pmod{101}6123(mod101),我们可以用标准的平方求幂方法来解决。

  • ​​用中国剩余定理进行分治:​​ 在像RSA这样的系统中,模数 nnn 是两个大素数的乘积,n=pqn=pqn=pq。​​中国剩余定理 (CRT)​​ 提供了另一个惊人的优化。与其直接计算 ae(modpq)a^e \pmod{pq}ae(modpq),我们可以在两个更小的、并行的宇宙中计算结果:找到 xp=ae(modp)x_p = a^e \pmod pxp​=ae(modp) 和 xq=ae(modq)x_q = a^e \pmod qxq​=ae(modq)。由于模数更小,这些计算要快得多。然后,CRT给了我们一个神奇的配方,将 xpx_pxp​ 和 xqx_qxq​ 缝合在一起,以找到模 nnn 的唯一答案。这是终极的“分而治之”策略。

普适的交响曲:从数到群

这是所有启示中最美妙的一个。这个算法其实并非关于数,而是关于运算。平方求幂在任何其运算满足​​结合律​​的数学系统中都有效——即 (a⋅b)⋅c=a⋅(b⋅c)(a \cdot b) \cdot c = a \cdot (b \cdot c)(a⋅b)⋅c=a⋅(b⋅c)。这样的系统被称为​​群​​。

考虑一下椭圆曲线这个奇妙的世界,它是另一种现代密码学的基础。在椭圆曲线上,“元素”是曲线上的点,“运算”是一种特殊的点加法,用 +++ 表示。要将一个点 PPP “求幂” kkk 次,我们将其与自身“相加” kkk 次。这写作 [k]P[k]P[k]P,称为标量乘法。

我们如何高效地计算 [123]P[123]P[123]P?我们使用完全相同的二进制逻辑!

  • 对一个数求平方(g⋅gg \cdot gg⋅g)变成了​​点倍加​​(P+P=[2]PP+P = [2]PP+P=[2]P)。
  • 乘法(ga⋅gbg^a \cdot g^bga⋅gb)变成了​​两点相加​​([a]P+[b]P[a]P + [b]P[a]P+[b]P)。

这个现在被称为​​倍加-累加​​(double-and-add)的算法,具有相同的结构:对于一个指数 kkk,你执行 O(log⁡k)\mathcal{O}(\log k)O(logk) 次点倍加和点相加来找到 [k]P[k]P[k]P。其基本原理是普适的。无论你是做整数乘法、曲线上点的加法,还是矩阵乘法,指数的二进制分解都提供了对数级的加速。这是对抽象代数统一力量的证明,展示了同一个优雅的思想如何在截然不同的数学世界中回响。从一个简单的数值捷径,平方求幂绽放成一曲由抽象结构、计算效率和密码安全谱写的交响乐。

应用与跨学科联系

我们已经见识了平方求幂的巧妙机制,这是一个将漫长乏味的乘法链条转变为简短、对数级飞跃的优美技巧。但这仅仅是一个数学上的奇观吗?一个用来向朋友炫耀大数计算的派对戏法?远非如此。这个简单的思想是现代计算的基石,它贯穿于密码学、系统模拟、图论等多个领域,揭示了我们在不同科学学科中解决问题方式上惊人的一致性。它是那种一旦被理解,就会改变你看待世界方式的罕见而强大的概念之一。

现代密码学的心跳

或许我们这个“飞跃”算法最直接、最关键的应用是在密码学领域——保密通信的科学。现代安全建立在一个迷人的不对称性之上:某些数学问题极难解决,但其解却很容易验证。平方求幂正是使“容易验证”这一部分成为可能的关键。

考虑离散对数问题:给定一个素数 ppp、一个底数 ggg 和一个值 hhh,找到一个指数 xxx 使得 gx≡h(modp)g^x \equiv h \pmod pgx≡h(modp)。对于大数而言,找到 xxx 被认为是极其困难的。但如果有人声称拥有解,我们如何检验他们的答案?我们必须计算 gx(modp)g^x \pmod pgx(modp) 并看它是否等于 hhh。如果 xxx 是一个有数百位的巨大数字,执行 x−1x-1x−1 次乘法将比宇宙的年龄还要长。然而,通过平方求幂,我们可以在与 xxx 的位数成正比的步数内计算出这个幂,而不是与 xxx 本身的值成正比。这种对数效率是“不可能”与“瞬间完成”之间的区别,也正是它使得验证加密密钥变得可行。

这个单一的操作,即快速模幂运算,是众多密码协议背后的主力。

  • ​​素性测试:​​ 安全的密码学需要巨大的素数。为了找到它们,我们不检查整除性。相反,我们使用像 Miller-Rabin 素性测试这样的概率性测试。该算法的核心步骤涉及检验一个同余式,这需要——你猜对了——为非常大的数计算 ad(modn)a^d \pmod nad(modn)。没有平方求幂,我们就无法高效地筛选候选数以找到保护我们数据所需的素数。

  • ​​模逆元:​​ 另一个基本操作是求模逆元。虽然扩展欧几里得算法提供了一种方法,但费马小定理给了我们另一种选择:aaa 模一个素数 ppp 的逆元就是 ap−2(modp)a^{p-2} \pmod pap−2(modp)。这将问题转化为了一个模幂运算问题。在计算机硬件上,除法比乘法昂贵得多,这种“基于乘法”的方法实际上可能比“基于除法”的欧几里得算法性能更好,这为工程师在设计高速密码库时提供了一个需要考虑的关键权衡。

  • ​​RSA 密码系统:​​ 著名的 RSA 算法保护着从你的信用卡交易到电子邮件的一切,它从根本上建立在模幂运算之上。加密和解密都只是:将一个数对一个大的合数 nnn 取模并求幂。一个在实践中常用的优美优化涉及中国剩余定理 (CRT)。我们不是计算一个单一的、对模数 nnn 的巨大幂运算,而是可以执行两个较小的、分别对 nnn 的素因子 ppp 和 qqq 取模的幂运算。因为幂运算的成本随数字大小的增长速度超过线性,这个技巧不仅仅是将工作量减半。通过将两个子问题的指数和模数的位长都减半,总工作量减少到原始工作量的约四分之一。这提供了一个惊人的4倍加速,证明了深刻的数论见解,在高效算法的驱动下,如何带来现实世界的性能提升。

这个思想的力量是如此普遍,以至于它甚至可以扩展到更抽象的数学对象。AKS 素性测试是第一个被证明能在多项式时间内确定素性的算法,它依赖于检验一个涉及多项式的同余关系。为了高效地做到这一点,它在一个特殊的多项式环内计算 (x+a)n(x+a)^n(x+a)n,这一壮举再次通过平方求幂得以实现。

递推与模拟的通用引擎

平方求幂的魔力并不局限于数字。它为理解任何根据固定的线性规则演化的过程提供了一个强大的工具。这类过程被称为线性递推,无处不在。

最著名的例子是斐波那契数列:Fn+1=Fn+Fn−1F_{n+1} = F_n + F_{n-1}Fn+1​=Fn​+Fn−1​。要找到第1000个斐波那契数,我们必须计算它前面的所有999个数吗?答案是响亮的“不”。我们可以用矩阵形式表达这个递推关系。一个简单的 2×22 \times 22×2 矩阵可以用来将数列从一步推进到下一步。因此,找到第 nnn 个斐波那契数就等同于将这个矩阵升到 nnn 次幂。通过对这个矩阵应用平方求幂,我们可以在对数级别的步数内跳到第 nnn 项。

这是一个深刻的认识。一个循序渐进的过程变成了一个单一的代数运算。这个原理远远超出了斐波那契数列的范畴。

  • ​​模拟物理系统:​​ 工程和物理学中的许多系统都由离散时间状态空间方程建模:系统在下一个时间步的状态是其当前状态的线性变换,写为 x[n+1]=Ax[n]x[n+1] = A x[n]x[n+1]=Ax[n]。因此,kkk 步之后的状态是 x[k]=Akx[0]x[k] = A^k x[0]x[k]=Akx[0]。为了预测一颗在轨卫星或一座振动桥梁在遥远未来的状态,我们不需要模拟中间的每一个微秒。我们可以使用平方求幂计算矩阵的幂 AkA^kAk,直接跳到未来的状态,从而节省大量的计算工作。

  • ​​揭示随机性:​​ 即使是像线性同余生成器(LCG)这样看似随机的东西——一种生成伪随机数的常用算法——也只是另一个递推关系:xk+1=(axk+c)(modm)x_{k+1} = (ax_k + c) \pmod mxk+1​=(axk​+c)(modm)。就像斐波那契数列一样,这可以转化为矩阵形式。这意味着我们可以使用矩阵求幂来即时计算序列中的第 nnn 个“随机”数,而无需生成中间的数。这个特性是一把双刃剑:它凸显了这些生成器的确定性和可预测性(使其不适用于密码学),但它也允许使用强大的技术,例如为大规模科学模拟并行生成随机数流的切片。

连接的几何学:网络中的路径

该算法的通用性延伸到了图论的视觉和结构领域。想象一个大型网络——社交网络、航线图或互联网。一个常见的问题是:从节点 iii 到节点 jjj 恰好走 kkk 步有多少种不同的方式?

人们可以尝试追踪所有路径,但对于大图和大的 kkk 值来说,这是一项极其复杂的任务。然而,有一种更优雅的方法。如果我们用邻接矩阵 AAA 来表示网络(如果从 iii 到 jjj 有直接链接,则 Aij=1A_{ij}=1Aij​=1),一个显著的性质便会浮现:矩阵 AkA^kAk 中的元素计算了每对顶点之间长度恰好为 kkk 的不同路径的数量。

要找到网络的远程连通性,我们需要计算这个矩阵的幂。对于大型网络,将 AAA 自身相乘 k−1k-1k−1 次的朴素方法会慢得无法接受。但是通过矩阵的平方求幂,我们可以在一个只随路径长度 kkk 的对数增长的时间内找到所有节点对的路径计数。这使我们能够以在计算上原本不可行的方式分析复杂网络的结构。

从数论和密码学的抽象世界,到物理系统的具体模型和图论的互联网络,平方求幂展示了一种深刻的统一性。它表明,一个单一、优雅的算法思想——通过重复平方实现飞跃的力量——可以应用于数字、矩阵甚至多项式,以解决大量各种各样的问题。这是一个美丽的提醒:在科学中,最深刻的洞见往往是那些连接起看似不相关事物的洞见。