二分求幂算法:快速完成求幂计算

二分求幂法是快速计算形如 a^b 的求幂运算的方法。朴素计算 a^b 的方式是将 a 连乘 b 次,代码如下:

int result = 1;
for (int i = 0; i != b; i++)
    result *= a;

这需要计算 b 次,而实际真的需要运算这么多次吗?答案是不需要,利用二分求幂法,我们可以使运算次数大大小于 b 次。那么什么是二分求幂法呢?我们先考虑一个具体的计算:a^{32} = \; ?

首先,我们需要想到:a^{32} = a^{16} \times a^{16}。这说明当我们计算出 a^{16} 的值后,再去计算 a^{32} 的值并不需要再连乘 a 十六次,我们只需要自乘一次 a^{16} 即可,这便大大降低了运算步骤。

如果我们想到了 a^{32} = a^{16} \times a^{16},那么我们也可以很自然的进一步想到 a^{16} = a^{8} \times a^{8},这同样大大简化了计算 a^{16} 的步骤:当我们得到 a^8 的值时,只需再自乘一次 a^8 即可得到 a^{16} 的值,而无需连乘 8 次 a。继续这样推演下去,我们可以得到以下的式子:
\begin{align} a^{32} &= a^{16} \times a^{16} \\[1ex] a^{16} &= a^{8} \times a^{8} \\[1ex] a^8 &= a^4 \times a^4 \\[1ex] a^4 &= a^2 \times a^2 \\[1ex] a^2 &= a \times a \\[1ex] a^1 &= a \end{align}
我们可以发现,通过二分求幂的方法,仅需 6 次运算即可得到结果,而在朴素求幂的方法中足足需要 32 次!

但我们发现一个问题:32 刚好是 2 的 5 次方,所以 32 可以一直被二分到 1 为止,如果失去这种特殊性,我们还可以使用二分求幂吗?答案也是可以的,以计算 a^{31} 为例:

\begin{align*} a^{31} &= a^{16} \times a^{15} \\[1ex] &= a^{16} \times a^8 \times a^7 \\[1ex] &= a^{16} \times a^8 \times a^4 \times a^3 \\[1ex] &= a^{16} \times a^8 \times a^4 \times a^2 \times a^1 \end{align*}

当我们得到上面的拆分结果后,再计算 a^{31} 就轻松多了。当我们得到 a 的值时,自乘一次即可得到 a^2,再自乘一次即可得到 a^4,再自乘一次得到 a^8,再自乘一次得到 a^{16},我们最后将这些中间结果乘到一起就计算出了 a^{31} 的值。利用二分求幂,计算出 a^{31} 仅需要 5 次运算,而朴素求幂足足需要 31 次!

现在我们已经充分认识到了二分求幂法的威力,但想要完全掌握这一方法,我们还需要攻克一个核心问题——如何正确的分解指数,使其可以满足二分求幂的运算过程(如上述对 a^{31} 的分解)。对于一般化的 a^b,我们可以这样考虑:
a^b = a^{b_1 + b_2 + b_3 + \cdots} = a^{b_1} \cdot a^{b_2} \cdot a^{b_3} \cdots \\[1ex] b_1 + b_2 + b_3 + \cdots = b \\[1ex] \lbrace b_1, b_2, b_3, \cdots \rbrace \subset \lbrace 1,2,4, 8, \cdots \rbrace = \lbrace 2^0, 2^1, 2^2, \cdots, 2^n \rbrace

显然,这样分解出来的指数 b_1, b_2, b_3, \cdots 满足二分求幂的运算过程。因为,在二分求幂过程中,从 a 开始不断自乘,我们可以得到:a^1, a^2, a^4, a^8, \cdots,所以,我们分解出来的 a^{b_1}, a^{b_2}, a^{b_3}, \cdots 必须属于自乘得到的序列,即指数 \lbrace b_1, b_2, b_3, \cdots \rbrace \subset \lbrace 1, 2, 4, 8, \cdots \rbrace,而 \lbrace 1, 2, 4, 8, \cdots \rbrace 又等于 \lbrace 2^0, 2^1, 2^3, 2^4, \cdots \rbrace,看到这里,我们只需要再迈出最后一步——联想到二进制,就可以完全掌握二分求幂法了。

对于任何一个指数 b,我们可以将其转化为二进制形式,这个二进制串中所有值为 1 的位置所代表的值,就是二分求幂法所需要的分解结果。现在用这样的视角再次回顾之前的 a^{31},指数 31 的二进制形式为 11111,这个二进制串从低位到高位每一个 1 的值如下:

\begin{aligned} 2^0 &= 1 \\[1ex] 2^1 &= 2 \\[1ex] 2^2 &= 4 \\[1ex] 2^3 &= 8 \\[1ex] 2^4 &= 16 \\[1ex] \end{aligned}

我们可以发现,这些值正好是分解后的结果,即 a^{31} = a^{16} \times a^8 \times a^4 \times a^2 \times a^1,然后就可以轻松的利用二分求幂法快速计算出结果了。

再分析一个具体的例子:计算 a^{177} 的值。指数 177 的二进制形式是 10110001,所有值为 1 的位置代表的值分别是:

\begin{aligned} 2^0 &= 1 \\[1ex] 2^4 &= 16 \\[1ex] 2^5 &= 32 \\[1ex] 2^7 &= 128 \end{aligned}

从而可以将 a^{177} 分解为 a^{128} \cdot a^{32} \cdot a^{16} \cdot a^1,然后利用二分求幂,从 a 开始,自乘一次得到 a^2,再自乘一次得到 a^4,以此类推,直到得到 a^{128}。这些中间结果中,对我们有用的是 a, a^{16}, a^{32}, a^{128},我们把它们乘到一起,就计算出了 a^{177} 的值。从程序运行的角度来看,利用二分求幂,得到这个结果需要循环 8 次,而如果要在朴素求幂算法中得到这一结果,则需要运行 177 次!显然,要计算的幂指数越大,二分求幂的优势也就愈加明显。

最后简单地用程序语言表达如何计算 a^b

int fun(int a, int b) {
    int result = 1;
    while (b) {
        if (b % 2 == 1)
            result *= a;
        a *= a;
        b /= 2;
    }
    return result;
}

参考资料:

  1. 王道论坛编组.王道论坛计算机考研机试指南[M]. :, 2013. 101-105.
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343

推荐阅读更多精彩内容

  • 网站乱码问题我们会经常碰到,大多见于非英文的中文字符或其他字符乱码,而且,这类问题常常是因为编码方式问题,主要原因...
    波段顶底阅读 2,817评论 1 9
  • 一、ECMAScript 一元运算符 一元运算符只有一个参数,即要操作的对象或值。它们是 ECMAScript 中...
    耦耦阅读 523评论 0 0
  • 运算符是处理数据的基本方法,用来从现有的值得到新的值。JavaScript 提供了多种运算符,本章逐一介绍这些运算...
    徵羽kid阅读 657评论 0 0
  • 定点小数运算 来自:http://www.eepw.com.cn/article/17893.htm 在DSP世界...
    郝宇峰阅读 8,999评论 0 2
  • 早晨,走出家门,迎面几簇樱花,一位满脸皱纹的老太太,颤微微地把身子凑向花丛,嗅着花香,回身看见我,一下子难为情了,...
    同谷曹山阅读 161评论 2 4