【算法】快速幂(JAVA版)

目录

引言

一、什么是快速幂

1.的另一种计算方法

2.指数n的分解

二、代码实现

1.得到各个二进制位

2.得到每个

总结


ID:HL_5461

引言

首先提一个问题:如何计算 2 ^ n ?

对于这个问题,相信很多人心里第一个想法是循环n次,每个循环里都对原数乘2。

这的确是一个简单快捷的方法,复杂度也只有O(N)。但是如果2^n中,n等于一亿,甚至更多呢?此时我们将循环一亿次。所以,有没有一个更好的方法呢?

当然是有的,就是我们接下来将认识的快速幂算法,如果使用快速幂算法,当n为一亿时,我们只需循环27次就可以了。


一、什么是快速幂

1.a^n的另一种计算方法

先举个例子,对 a^n 当 n = 16 时。

用普通的方法,很显然,我们需要循环16次。但我们不妨换一个思路:

a^1 \times a ^1 = a^2
a^2 \times a^2 = a^4
a ^4 \times a^4 = a^8
a^8 \times a^8=a^{16}

对于上标n,我们使用每次翻倍的方法,直到n为16。这样的话,我们只要循环4次就行了,时间复杂度也由原来的O(N)减小到O(logN)。

就这么简单,本文到此结束(bushi)。

好吧,估计很多人会有疑问:这样的话,我们难道只能计算n为2的幂的情况吗?像n为27,n为41这种非幂的情况都算不了,这个算法岂不是很鸡肋?

当然不是,但在向后介绍之前,我们先明确这样一个事实:对于a^{n} (其中n可写成2^{n_{0}},即,2

的多少幂形式)的幂函数,我们可以用如上方法快速求出

接下来是n不是2的幂的情况:对于非2的幂的情况,我们都可将其拆为2的幂相加的情况。以n = 41,即 a^{41} 为例:

2^0 = 1\bigstar
2^1 = 2
2^2 = 4
2^3=8\bigstar
2^4 = 16
2^5 = 32\bigstar
\bigstar为要选择的幂)

n = 41 = (1+8+32),也就说a^{41} = a^1\times a^8\times a^{32} ,由于1,8,32都可以写成如上表的2的幂的形式,所以可以根据上文所诉的方法很快地算出。

由此,将一个数分解为2的幂之和即为快速幂算法的关键。

2.指数n的分解

其实前面将n=41分解为1+8+32时,一些对二进制比较熟悉的人应该已经想到了,我们还是以41为例,将其转换为二进制,即101001:

101001
\bigstar\bigstar\bigstar
2^5 =322^4 = 162^3=82^2 = 42^1 = 22^0 = 1

结果很明了了,要想知道如何将n分解为各个2的幂相加的形式,我们只需将其转化为二进制,就能很容易将其分解为2的幂之和。


二、代码实现

1.得到各个二进制位

很显然,首先我们需要一个循环,循环条件我们后面讨论。回忆一下初学二进制时了解到的十进制转化二进制的方法,还是以41为例:

公式余数2的幂
41\div 22012^0
20\div 21002^1
10\div 2502^2
5\div 2212^3
2\div 2102^4
1\div2012^5

显然的,循环结束的条件是商为0,我们将其看做 n /= 2 而每次 n % 2 得数即为该位2的幂。

先实现这一小段代码:

int r = 0;//记录余数
while (n != 0) {
	r = n % 2;//取余数,即二进制位
	n /= 2;	
}

当然我们也可以用位运算的方法:

n % 2 相当于n做位与运算,n / 2 相当于n右移一位。还是以41为例:

公式余数2的幂
41\div 2

20

(101001 >>  1 = 10100)

1

(101001 & 1 = 1)

2^0
20\div 2

10

(10100 >> 1 = 1010)

0

(10100 & 1 = 0)

2^1
10\div 2

5

(1010 >> 1 = 101)

0

(1010 & 1 = 0)

2^2
5\div 2

2

(101 >> 1 = 10)

1

(101 & 1 = 1)

2^3
2\div 2

1

(10 >> 1 = 1)

0

(10 & 1 = 0)

2^4
1\div2

0

(1 >> 1 = 0)

1

(1 & 1 = 1)

2^5

所以上面那段代码也可以变为如下形式:

int r = 0;//记录余数
while (n != 0) {
	r = n & 1;//取余数,即二进制位
	n >>= 1;	
}

2.得到每个a^n

巴拉了半天,别忘了我们最初的目的,我们计算的是a^n,还是以n为41为例,a^{41} = a^1\times a^8\times a^{32}。我们有了每个二进制位,如何控制如1,8,32这些指数呢?我们不妨每次循环都让 a = a * a:

公式余数a的值
41\div 2201a^1
20\div 2100a ^2
10\div 250a^4
5\div 221a^8
2\div 210a^{16}
1\div201a^{32}

着重关注余数和a的值,是不是就很明显了?当余数不为0,余数乘此时a的值,直到循环结束将所得的所有值相乘即为a^n

OK,上代码:

public static int qmi(int a, int n) {//a为底数,n为指数

    int ans = 1;//记录乘积

    while (n != 0) {
        if ((n & 1) != 0) {//当余数不为0
            ans *= a;//乘上此时的a
        };
        n >>= 1;//n右移一位
        a = a * a;//a变为此时的a^2
    }
    return ans;
}

总结

我们从头到尾再梳理一遍,这次换个例子,以3^6为例:

首先定义一个记录乘积的ans,6不为0,进入循环,采用位运算或者模的方式,得到其余数0,0 == 0,不等于不成立,不做处理,然后采用位运算或者除的方式,n变为3,a变为a的二次方,为,3^2

再次循环,余数为1,1 != 0,进入if,ans = 3^2,n为1,a为3^4

继续循环,余数为1,进入if,ans = 3^2 * 3^4,n为0,a为3^8

循环不成立,结束返回此时ans,即 3^2 * 3^4

真心希望这篇博客能对大家有所帮助,若有错误,欢迎大家批评斧正!

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

是兰兰呀~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值