首页 > 技术文章 > 快速幂

chantmee 2020-11-10 18:47 原文

快速幂的概念

幂运算 an 即为na相乘,用普通的\(for\)循环计算复杂度为,\(o(n)\)当非常\(n\)大的时候就不能采取这种方式了。快速幂可以较高效的计算出结果。

快速幂的基本思路是分治:将an变为(a2)n/2,这样仅仅将a变为a2,运算量就减小了一半,重复上面的操作就可以将复杂度从\(o(n)\)优化为\(o(lg(n))\)了。

#include <iostream>

typedef long long ll;

const int mod = 1e9 + 7;

ll qPow(ll a, ll n) {
    if (n == 1) {
        return a;
    } else {
        ll t = qPow(a, n >> 1);
        t = t * t % mod;
        if (n & 1) {
            t = t * a;
        }
        return t % mod;
    }
}

int main() {
    ll a = 5;
    ll n = 100000000000000000;
    std::cout << qPow(a, n) << std::endl;

    return 0;
}

上面为递归版本,由于递归层数最多为\(lgn\)所以不会出现爆栈的情况。

但是由于递归会将大量信息存储在堆栈中,再加上不断的寻址,就导致时间和空间上的浪费,下面用迭代的方法进行再优化。

这里用a13作为例子,将a13化为a8a4a1,此时注意到a1a1=a2,a2a2=a4,a4*a4=a8,他们的幂都是2的倍数,只需要不断递推就可以求出这些数。

但是8,4,1这些数字是怎么得到的呢?这里用到了二进制数字进行理解。二进制数字中,每一位的数字是低一位数字的两倍 1, 1310=11012 ,那么一开始设置一个\(x = 1\),从\(1101\)的最低位开始,每次乘\(n\)个2(0<=n<4),当前位为1的时候,\(x\)就是我们要得到的数字之一。

从第0位到第3位:

x = 1 * 20=1,当前位为1,所以得到一个x = 1。

x = 1 * 20 * 2=2,当前位为0,没有得到任何数字。

x = 1 * 20 * 2 * 2=4,当前位为1,所以得到一个x = 4。

x = 1 * 20 * 2 * 2 * 2=8,当前位为1,所以得到一个x = 8。

在程序中用>>来右移一位,再用&来判断当前为是否为1.

#include <iostream>

typedef long long ll;
const int mod = 1e9 + 7;

ll qPow(ll a, ll n) {
    ll res = 1;
    ll bash = a;
    while (n) {
        if (n & 1) {
            res = res * bash % mod;
        }
        bash = bash * bash % mod;
        n >>= 1;
    }
    return res;
}

int main() {
    ll a = 5;
    ll n = 100000000000000000;
    std::cout << qPow(a, n) << std::endl;
    return 0;
}

快速幂的取模

由于幂运算的结果往往都非常大,所以通常情况下都需要进行取模操作。

根据模运算的性质,可以得到a n mod m = (a mod m) n mod m。这一步操作对应着上面代码的11行和13行。

推荐阅读