客户端基本不用的算法系列:快速幂

640?wx_fmt=png

幂运算是我们平时写代码的时候最常用的运算之一。根据幂运算的定义我们可以知道,如果我们要求 xN 次幂,那么想当然的就会写出一个 N 次的循环,然后累乘得到结果。所以我们要求幂运算的复杂度仍旧是 O(N)?

那么有没有一种更快的方法呢?

这里给出一种在计算机领域常用的快速幂算法,将 O(N) 降为 O(logN)

我通过例子来讲解这个优化过程:

假设我们要算 xn 次幂,使用累乘来编写代码:

1res = 1
2for i in range(n):
3    res *= x

好的,我们已经完成了 O(N) 的解法。

二进制拆分

为了优化这个算法,我们接下来进行数学推导:

我们继续思考当 N = 10 这个具体场景,我们可以把 10 写成二进制来表示 1010(BIN),然后我们模拟一次二进制转十进制的过程(复习一下大学知识)

640?wx_fmt=png

我用下划线把二进制的 1010 标识出来,这样大家就可以发现二进制和十进制转换时的代数式规律。

继续回想刚才的场景,那么我们求 x10 次幂,则式子我们可以写成这样:

640?wx_fmt=png

我们按照二进制低位到高位从左往右交换一下位置:

640?wx_fmt=png

我们关注相邻的两项,如果我们不考虑幂指数的 *0*1 ,我们只看前半部分,会发现有这么一个规律:

640?wx_fmt=png

也就是说,不考虑幂指数的 *0*1 右式,左式每次只要每次乘以自身,就是下一项的左式。在我们的例子中其实就是:

640?wx_fmt=png

用编程思维来考虑这个问题,只要我们从 x 开始维护这么一个左式,每一次迭代都执行 x *= x,然后每次遇到右边是 *1 的情况,就记录一下 res *= x 是不是就能模拟咱们二进制拆分的计算思路了呢?

编程实现一下 x 的 10 次方

我们用上面的思路,通过代码来计算一下 2 的 10 次方,答案应该是 1024

 1#include <iostream>
 2using namespace std;
 3
 4int main() {
 5    int n = 10; // 幂指数,下面通过二进制拆分成 1010
 6    int x = 2; // 底数
 7    int res = 1; // 累乘的答案
 8    while (n) {
 9                // 去除二进制的最低位,也就是上面推导中的右式,如果 n & 1 == 1,说明是 *1
10        if (n & 1) {
11                        // 如果是 *1,则根据我们观察出来的规律,对维护的结果做累乘
12            res *= x;
13        }
14                // 转换到下一位
15        x *= x;
16                // 二进制右移一位,目的是取到下一个低位二进制
17        n >>= 1;
18    }
19    cout << res << endl; // 1024
20    return 0;
21}

是不是发现非常的简单!我们至此已经实现了快速幂算法。我们将  n, x 做成参数,编写一个快速幂的方法:

 1#include <iostream>
 2using namespace std;
 3
 4int qpow(int x, int n) {
 5    int res = 1;
 6    while (n) {
 7        if (n & 1) res *= x;
 8        x *= x;
 9        n >>= 1;
10    }
11    return res;
12}
13
14int main() {
15    cout << qpow(2, 10) << endl; // 1024
16    cout << qpow(4, 2) << endl;  // 16
17    cout << qpow(5, 3) << endl;  // 125
18    cout << qpow(10, 6) << endl; // 1000000
19    return 0;
20}

复杂度

通过上面对幂指数的拆分,发现快速幂只需要循环拆分的项数就可以完成整个幂运算。

我们不妨设求 x 的 N 次方,并且令 x 的所有二进制位都为 1,就可以得到下面这个等式:

640?wx_fmt=png

那么其实,k 就是计算机需要计算的次数,也就是时间复杂度。套入公比是 1 的等比数列前 k 项和来反推 k 的大小:

640?wx_fmt=png

好了,这就是快速幂的全部内容了。你可以使用这道题的知识来求解 LeetCode 372. Super Pow(点击原文直达) 。下一篇文我们来手把手 AC 这题

640?wx_fmt=png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值