快速幂算法

1 简介

​ 快速幂(平方求幂)算法,是一种计算乘方的简单有效的算法,它可以在O(log n)的时间复杂度下进行求幂操作。快速幂算法的数学表示如下:

\[a^n = \begin{cases} a^{n - 1} * a, &\mbox{if n is odd}\\ a^{\frac{n}{2}} * a^{\frac{n}{2}}, &\mbox{if n is even but not 0} \\ 1, &\mbox{if n = 0} \end{cases} \]

2 分析

​ 为什么快速幂算法能够在O(log n)的时间复杂度下工作呢?

​ 我们以\(2^{10}\)为例进行分析:

方法1:最朴素的想法,\(2*2=4,4*2=8, ...,\) 一步一步算,共进行了9次乘法。

​ 这样算无疑太慢了,尤其对计算机的CPU而言,每次运算只乘上一个个位数,无疑太屈才了。这时我们想到,也许可以拆分问题。

方法2:先算2的5次方,即\(2*2*2*2*2\),再算它的平方,共进行了5次乘法。

​ 但这并不是最优解,因为对于“2的5次方”,我们仍然可以拆分问题。

方法3:先算\(2*2\)得4,则2的5次方为\(4*4*2\),再算它的平方,共进行了4次乘法。

​ 模仿这样的过程,我们得到一个在\(O(\log n)\)时间内计算出幂的算法,也就是快速幂

3 算法实现

递归实现:

public int qpow(int a, int n) {
    if(n == 0) {
        return 1;
    }else if(n % 2 == 1) {
        return qpow(a, n - 1) * a;
    } else {
        int temp = qpow(a, n / 2); // 注意不能直接return qpow(a, n / 2) * qpow(a, n / 2), 否则会退化为O(n)。
        return temp * temp;
    }
}

非递归实现

public int qpow(int a, int n) {
    int ans = 1;
    while(n != 0) {
    	if((n & 1) != 0) { // 判断二进制数的末位为0还是为1
        	ans *= a;
    	}
    	n >>= 1; // 将二进制数右移一位,也相当于/2
    	a *= a;   
    }
    return ans;
}

​ 非递归形式比较难以理解,作如下解释(以\(2^{10}\)为例):

我们将幂指数n以二进制形式表示,则\(2^{10} = 2^{(1010)_2}\) ,我们可以将其拆成\(2^{(1000)_2} * 2^{(10)_2}\)。对于任意整数,我们都可以做类似的拆分。

最初ans为1,然后我们一位一位算:

1010的最后一位是0,所以\(a^1\)这一位不要。然后1010变为101,a变为\(a^2\)

101的最后一位是1,所以\(a^2\)这一位是需要的,乘入ans。101变为10,a再自乘。

10的最后一位是0,跳过,右移,自乘。

然后1的最后一位是1,ans再乘上\(a^8\)。循环结束,返回结果。

针对如上所述过程,我们总结如下:

  • 如果幂指数二进制表示的末位数为1,则需要将此时的a乘入ans中;

  • 如果末位数为0, 则无需将a乘入ans,但是需要让\(a = a^2\)

a依次变为\(a^1, a^2, a^4, a^8, ....\),只要对应的二进制末位数为1就将其乘入到ans中,否则就不乘入。这样对于\(a^n\)的求解,我们只需做\(\log_2{n}\)次运算即可,从而达到\(O(\log n)\)的时间复杂度。

posted @ 2022-04-13 10:39  Xiao·Tong  阅读(283)  评论(0编辑  收藏  举报