快速幂
求数 a a a的 n n n次幂,可以采用二分法进行快速计算,即
a n = { a n 2 ⋅ a n 2 , n 为偶数 a ⋅ a n 2 ⋅ a n 2 , n 为奇数 a^n=\left\{\begin{array}{ll} a^{\frac{n}{2}}\cdot a^{\frac{n}{2}}, & n为偶数\\ a\cdot a^{\frac{n}{2}}\cdot a^{\frac{n}{2}}, & n为奇数 \end{array}\right. an={a2n⋅a2n,a⋅a2n⋅a2n,n为偶数n为奇数
还可以这样考虑,将
n
n
n表示为二进制,每一个进制位决定了是否取这一项的结果,且每一项可以由前一项平方迭代计算
a
n
=
a
(
b
0
b
1
b
2
b
3
⋯
b
k
)
2
=
a
b
0
2
0
+
b
1
2
1
+
b
2
2
2
+
b
3
2
3
+
⋯
b
k
2
k
=
a
1
b
0
⋅
a
2
b
1
⋅
a
4
b
2
⋅
a
8
b
3
⋯
a
b
k
2
k
=
a
b
0
⋅
(
a
⋅
a
)
b
1
⋅
(
a
2
⋅
a
2
)
b
2
⋅
(
a
4
⋅
a
4
)
b
3
⋯
(
a
2
k
−
1
⋅
a
2
k
−
1
)
b
k
\begin{aligned}a^n&=a^{(b_0b_1b_2b_3\cdots b_k)_2}\\ &=a^{b_02^0+b_12^1+b_22^2+b_32^3+\cdots b_k2^k}\\ &=a^{1b_0}\cdot a^{2b_1}\cdot a^{4b_2}\cdot a^{8b_3}\cdots a^{b_k2^k}\\ &=a^{b_0}\cdot (a\cdot a)^{b_1}\cdot (a^2\cdot a^2)^{b_2}\cdot (a^4\cdot a^4)^{b_3}\cdots (a^{2^{k-1}}\cdot a^{2^{k-1}})^{b_k}\end{aligned}
an=a(b0b1b2b3⋯bk)2=ab020+b121+b222+b323+⋯bk2k=a1b0⋅a2b1⋅a4b2⋅a8b3⋯abk2k=ab0⋅(a⋅a)b1⋅(a2⋅a2)b2⋅(a4⋅a4)b3⋯(a2k−1⋅a2k−1)bk
public int power(int a, int n){
int result = 1;
while (n > 0) {
if (n % 2 == 1) { // 还可以用 n & 1 == 1,表示取二进制最后一位
result *= a; // 二进制最后一位为1,表示取当前a
}
n /= 2; // 还可以用 n = n >> 1,表示二进制右移一位
a *= a; // 迭代计算下一位的基数
}
return result;
}
变形:如果对要求 a n % k a^n \% k an%k,对k取余操作可以直接放进每步迭代里
public int power(int a, int n, int k){
int result = 1;
while (n > 0) {
if (n % 2 == 1) {
result = (result * a) % k; // 对k取余
}
n /= 2;
a = (a * a) % k; // 对k取余
}
return result;
}
矩阵快速幂
和上述思路完全一样,只是全部对于矩阵乘法。斐波那契数列定义如下
f ( n ) = { 0 , n = 0 1 , n = 1 f ( n − 2 ) + f ( n − 1 ) , n ≥ 2 f(n)=\left\{\begin{array}{ll} 0, & n=0\\ 1, & n=1\\ f(n-2)+f(n-1), & n\ge2 \end{array}\right. f(n)=⎩ ⎨ ⎧0,1,f(n−2)+f(n−1),n=0n=1n≥2
可以构造矩阵 A = [ 1 1 1 0 ] A=\left[\begin{array}{cc}1 \quad 1\\ 1 \quad 0\end{array}\right] A=[1110],则 [ f ( n + 1 ) f ( n ) f ( n ) f ( n − 1 ) ] = A n \left[\begin{array}{cc}f(n+1) \quad f(n)\\ f(n) \quad f(n-1)\end{array}\right]=A^n [f(n+1)f(n)f(n)f(n−1)]=An。其中 A n A^n An就可以使用矩阵快速幂计算。
public class Fibonacci {
static int[][] dot(int[][] A, int[][] B) {
int Arows = A.length;
int Acols = A[0].length;
int Brows = B.length;
int Bcols = B[0].length;
assert (Acols == Brows);
int tmp;
int[][] R = new int[Arows][Bcols];
for (int i = 0; i < Arows; i++) {
for (int j = 0; j < Bcols; j++) {
tmp = 0;
for (int k = 0; k < Acols; k++) {
tmp += A[i][k] * B[k][j];
}
R[i][j] = tmp;
}
}
return R;
}
static int fibonacci(int n) {
if (n == 0) return 0;
n -= 1;
int[][] result = new int[][]{{1, 0}, {0, 1}};
int[][] A = new int[][]{{1, 1}, {1, 0}};
while (n > 0) {
if (n % 2 == 1) {
result = dot(result, A);
}
n /= 2;
A = dot(A, A);
}
return result[0][0];
}
public static void main(String[] args) {
System.out.println(fibonacci(100000));
}
}