一、快速幂
1.背景
快速幂其实是一种用于快速计算出 x n x^n xn的算法,其中指数n比较大。
2.对比
- 传统求幂算法:时间复杂度为O(指数n)
- 快速幂算法:核心思想是每一步都把指数分成两半,而相应的底数做平方运算,这样不仅能把非常大的指数给不断变小,所需要执行的循环次数也在变小,而最后的结果却一直不会变。
3.快速幂的理解
①从二进制的角度进行理解
对于任意的十进制整数n,假设其二进制表示为“
b
m
.
.
.
b
3
b
2
b
1
b_m ... b_3 b_2 b_1
bm...b3b2b1”,则有二进制转十进制:
n
=
2
0
b
1
+
2
1
b
2
+
2
2
b
3
+
.
.
.
+
2
m
−
1
b
m
n = 2^0b_1+2^1b_2+2^2b_3+...+2^{m-1}b_m
n=20b1+21b2+22b3+...+2m−1bm
从而有x的二进制展开为:
x
n
=
x
2
0
b
1
+
2
1
b
2
+
2
2
b
3
+
.
.
.
+
2
m
−
1
b
m
=
x
2
0
b
1
⋅
x
2
1
b
2
⋅
x
2
2
b
3
⋅
.
.
.
⋅
x
2
m
−
1
b
m
x^n = x^{2^0b_1+2^1b_2+2^2b_3+...+2^{m-1}b_m}=x^{2^0b_1}·x^{2^1b_2}·x^{2^2b_3}·...·x^{2^{m-1}b_m}
xn=x20b1+21b2+22b3+...+2m−1bm=x20b1⋅x21b2⋅x22b3⋅...⋅x2m−1bm
根据上述的推导,可以把计算
x
n
x^n
xn转化成解决以下两个问题:
- 计算 x 2 0 , x 2 1 , x 2 2 , . . . , x 2 m − 1 x^{2^0},x^{2^1},x^{2^2}, ... ,x^{2^{m-1}} x20,x21,x22,...,x2m−1:循环赋值 x = x 2 x = x^2 x=x2即可
- 获取二进制各位 b 1 , b 2 , b 3 , . . . , b m b_1,b_2,b_3,...,b_m b1,b2,b3,...,bm的值:循环执行如下的操作
n&1(与操作):判断二进制位的最后一位是否为1
n>>1(移位操作):n右移一位(可理解为删除二进制位的最后一位)
因此,应用以上操作,可在循环中依次计算 x 2 0 , x 2 1 , x 2 2 , . . . , x 2 m − 1 x^{2^0},x^{2^1},x^{2^2}, ... ,x^{2^{m-1}} x20,x21,x22,...,x2m−1的值,并将这些值相乘即可。
②从二分法的角度进行理解
快速幂实际上是二分思想的一种应用。
二分推导:
x
n
=
x
n
/
2
x^n = x^{n/2}
xn=xn/2 ·
x
n
/
2
x^{n/2}
xn/2,若要令
n
/
2
n/2
n/2为整数,则需要分n为奇数和偶数两种情况:
- 当n为偶数: x n = ( x 2 ) n / / 2 x^n = (x^2)^{n//2} xn=(x2)n//2
- 当n为奇数: x n = x ⋅ ( x 2 ) n / / 2 x^n = x · (x^2)^{n//2} xn=x⋅(x2)n//2,会比多出一项x
根据二分推导,可以通过循环 x = x 2 x = x^2 x=x2的操作,每次把幂从n降为n//2,直至将幂降为0。
设sum=1,设初始状态 x n = x n ⋅ s u m x^n = x^n · sum xn=xn⋅sum。在循环二分的时候,当n为偶数,底数由 x x x变为 x 2 x^2 x2,指数 n n n变为 n / / 2 n//2 n//2;当n是奇数时, s u m = s u m ∗ x sum = sum * x sum=sum∗x底数由 x x x变为 x 2 x^2 x2,指数 n n n变为 n / / 2 n//2 n//2。
算法执行流程如下:
- 当x==0时:直接返回0
- 初始化sum=1
- 当n < 0:把问题转化为n >= 0的范围内,即x = 1/x,n = -n
- 循环计算(当n == 0结束循环):当n&1 == 1,则sum *= x —>x = x^2 —>n >> 1
二、矩阵快速幂
矩阵快速幂实际上就是将快速幂算法里的数字换成矩阵,
矩阵快速幂算法 = 矩阵乘法 + 快速幂算法
应用实例1:斐波那契数列—
f
(
n
)
=
f
(
n
−
1
)
+
f
(
n
−
2
)
f(n) = f(n-1) + f(n-2)
f(n)=f(n−1)+f(n−2)
[
f
(
n
)
f
(
n
−
1
)
]
=
[
1
1
1
0
]
[
f
(
n
−
1
)
f
(
n
−
2
)
]
=
.
.
.
=
[
1
1
1
0
]
n
−
1
[
f
(
1
)
f
(
0
)
]
\begin{gathered} \begin{bmatrix} f(n) \\ f(n-1) \end{bmatrix} = \begin{bmatrix} 1&1 \\ 1&0 \end{bmatrix}\begin{bmatrix} f(n-1) \\ f(n-2) \end{bmatrix}=...= \begin{bmatrix} 1&1 \\ 1&0 \end{bmatrix}^{n-1}\begin{bmatrix} f(1) \\ f(0) \end{bmatrix} \quad \end{gathered}
[f(n)f(n−1)]=[1110][f(n−1)f(n−2)]=...=[1110]n−1[f(1)f(0)]
应用实例2:
f
(
n
)
=
a
f
(
n
−
1
)
+
b
f
(
n
−
2
)
+
c
f(n) = af(n-1) + bf(n-2)+c
f(n)=af(n−1)+bf(n−2)+c
[
f
(
n
)
f
(
n
−
1
)
c
]
=
[
a
b
1
1
0
0
0
0
1
]
[
f
(
n
−
1
)
f
(
n
−
2
)
c
]
=
.
.
.
=
[
a
b
1
1
0
0
0
0
1
]
n
−
1
[
f
(
1
)
f
(
0
)
c
]
\begin{gathered} \begin{bmatrix} f(n) \\ f(n-1)\\c \end{bmatrix} = \begin{bmatrix} a&b&1 \\ 1&0&0 \\ 0&0&1 \end{bmatrix}\begin{bmatrix} f(n-1) \\ f(n-2) \\ c\end{bmatrix}=...= \begin{bmatrix} a&b&1 \\ 1&0&0 \\ 0&0&1 \end{bmatrix}^{n-1}\begin{bmatrix} f(1) \\ f(0) \\ c\end{bmatrix} \quad \end{gathered}
⎣⎡f(n)f(n−1)c⎦⎤=⎣⎡a10b00101⎦⎤⎣⎡f(n−1)f(n−2)c⎦⎤=...=⎣⎡a10b00101⎦⎤n−1⎣⎡f(1)f(0)c⎦⎤