快速幂是一种用以快速计算整数或矩阵的幂的一种算法。
整数快速幂
如果要计算一个数x的二次方,可以使用x*x,三次方则是三个x相乘,n次方便是n个x相乘,按照这种直观的算法可以写如下代码:
const pow = (x, n) => {
if (!n) return 1;
let ans = x;
for(let i=1;i<n;i++) {
ans *= x;
}
return ans;
}
显然,这个算法的时间复杂度为O(n),空间复杂度为O(1)。在进行低次幂的运算时,这个方法没有问题,不快,但是还可以接受,但是在计算1000000000次幂,效率还是会很低下。是否有什么方法可以优化一下这个算法:
我们发现,在求x^4时,可以先算const a = x*x, 接着a * a便可以得到结果,这样只是计算了2次,而不需要计算4次。计算17次幂的时候可以 x^17=x^8 * x^8 * x,计算出来一个x^8之后再自身相乘便能计算16次方,再乘以x可得到17次方的结果,这样完全不需要进行17次的计算。根据这个思想,算法如下:
const quickPow = (x, n) => {
let ans = 1;
let res = x;
while (n) {
// 最低位为1, ans乘以当前权重 res
if (n & 1) {
ans *= res;
}
// 计算下一次循环的权重
res *= res;
// 右移一位,准备下一次
n >>= 1;
}
return ans;
};
下面已2^19为例分析一下这个算法:
19的二进制表示为10011,
| 1 | 0 | 0 | 1 | 1 |
|---|---|---|---|---|
2^16=65536 | 2^8=256 | 2^4=8 | 2^2=4 | 2^1=2 |
上述代码中res就是各个位上的权重,并且高位的权限等于低一位的权重的二次方如
2^16 = 2^8 * 2^8 = 256 * 256,上述算法就是对每一位为1的数进行相乘,2^19 = 2^16 * 2^2 * 2。
矩阵快速幂
将矩阵快速幂之前,先提一个问题,如何求斐波那契数列第n项?
所谓斐波那契数列就是
f(0) = 0
f(1) = 1
f(n) = f(n-1) + f(n-2) n>=2
根据状态转移方程并利用滚桶转移思想可以写出以下代码:
const fibo = (n) => {
if (n<0) throw new Error('n can not less then 0')
if (n === 0) return 0;
if (n === 1) return 1;
let fn_1 = 1;
let fn_2 = 0;
let fn;
for (let i=2;i<=n;i++) {
fn = fn_1 + fn_2;
fn_2 = fn_1;
fn_1 = fn;
}
return fn;
}
显然时间复杂度为O(n), 空间复杂度为O(1), 在n比较大时,效率还是比较低的,可以使用矩阵快速幂对其进行优化。
上面的等式是否成立呢?可以计算一下:
可以知道上面的等式是成立的,那进行下一步的推导
令
故
所以f(n)为 的左上角的值。这个时候可以使用矩阵快速幂了:
const matrixMultiply = (a, b) => {
const c = [[0,0],[0,0]];
for(let i=0; i<2;i++) {
for (let j=0;j<2;j++) {
c[i][j] = a[i][0] * b[0][j] + a[i][1]*b[1][j];
}
}
return c;
}
const matrixPow = (x, n) => {
let ans = [[1,0],[0,1]];
let res = x;
while(n) {
if (n&1) {
ans = matrixMultiply(ans, res);
}
res = matrixMultiply(res, res);
n>>=1;
}
return ans;
}
const matrixFibo = (n) => {
if (n<0) throw new Error('n can not less then 0')
if (n ==0) return 0;
if (n === 1) return 1;
const res = matrixPow([[1,1],[1,0]], n-1);
return res[0][0];
}
总结
是怎么想到使用矩阵来解斐波那契数列?
一般情况下,形如 的状态转移方程都可以转换为这样的矩阵的幂求解,转换之后可以使用快速幂获得优化。