矩阵快速幂

一、快速幂

实现 pow(x, n) ,即计算 x 的整数 n 次幂函数(即,xn )。

对于上面这道题,我们最容易想到的解法就是通过循环或递归进行累乘求解

public double MyPow(double x, int n)
{
	long N = n;
	if (N == 0)
		return 1;
	double res = x;
	long num = N > 0 ? N : -N;
	for (long i = 1; i < num; i++)
	{
		res *= x;
	}

	if (N < 0)
		res = 1 / res;
	return res;
}

然而,这种解法在面对n比较大的情况时,效率会变得十分低下。仔细想想上面的循环过程就会发现,里面进行了很多次无意义的计算。以 a 10 a^{10} a10举例,通过循环一个一个往上乘,需要进行9次运算。而如果我们先计算出 a 2 a^2 a2(1次),然后计算 a 2 ∗ a 2 ∗ a 2 ∗ a 2 ∗ a 2 a^2*a^2*a^2*a^2*a^2 a2a2a2a2a2(4次),总计算量就能减少到5次。如果先计算出 a 4 a^4 a4(2次),再计算 a 2 ∗ a 4 ∗ a 4 a^2*a^4*a^4 a2a4a4(2次),总计算量就能减少到4次。

也就是说,将 a n a^n an拆解成 a x ∗ a y ∗ a z . . . a^x*a^y*a^z... axayaz...的形式就可以降低总体运算的次数。那么如何拆解n才最高效呢?根据前面的思路,显然是每次平方所需的运算次数最少。仍然是 a 10 a^{10} a10的例子,我们从 a 1 a^1 a1开始,每次取平方值,可以得到 [ a 1 , a 2 , a 4 , a 8 ] [a^1,a^2,a^4,a^8] [a1,a2,a4,a8]。想要组成 a 10 a^{10} a10,只需要让 a 8 ∗ a 2 a^8*a^2 a8a2即可。所需的计算次数也是4次。看到这里有没有觉得熟悉?我们实际上就是在把n拆成几个 2 ? 2^? 2?相加,也就是类似于n的二进制转换成十进制的过程。

下面仍然以 a 10 a^{10} a10为例,通过图展示一下计算过程:
我们从10的最低位开始遍历,遇到的第一位是0,对应的是 a 1 a^1 a1,但我们并不需要它计算结果,所以跳过。

接下来遇到的第二位是1,对应 a 2 a^2 a2,这是计算结果需要用到的,所以把它乘到res上。继续移动指针。

遇到的第三位是0,对应 a 4 a^4 a4,用不到,跳过。

遇到的第四位是1,对应 a 8 a^8 a8,需要用到,把它乘到res中。

res便是最终结果。将上面的思路转换成代码(未考虑特殊情况):

private double Pow(double a, int n)
{
	double res = 1;
	while (n !=0)
	{
		// 当前位为1,计入结果
		if ((n & 1) == 1)
			res = res * a;
		// a自乘
		a *= a;
		// n右移
		n >>= 1;
	}
	return res;
}

二、矩阵快速幂

矩阵快速幂的一个非常典型的应用就是斐波那契数列(原题地址)。先来看斐波那契数列的定义:
F ( 0 ) = 0 , F ( 1 ) = 1 F(0) = 0, F(1) = 1 F(0)=0,F(1)=1
F ( N ) = F ( N − 1 ) + F ( N − 2 ) , 其中 N > 1. F(N) = F(N - 1) + F(N - 2), 其中 N > 1. F(N)=F(N1)+F(N2),其中N>1.
我们先定义一个矩阵
[ F ( N ) F ( N − 1 ) ] \begin{bmatrix} F(N) & F(N-1) \end{bmatrix} [F(N)F(N1)]
现在假设有一个变换矩阵 M M M,与上述矩阵存在如下关系

[ F ( N ) F ( N − 1 ) ] × M = [ F ( N + 1 ) F ( N ) ] \begin{bmatrix} F(N) & F(N-1) \end{bmatrix}×M= \begin{bmatrix} F(N+1) & F(N) \end{bmatrix} [F(N)F(N1)]×M=[F(N+1)F(N)]

很显然, M M M是一个 2 × 2 2×2 2×2的矩阵,我们假设
M = [ a b c d ] M= \begin{bmatrix} a & b \\ c & d \end{bmatrix} M=[acbd]
带入上面的公式可得
[ F ( N ) F ( N − 1 ) ] × [ a b c d ] = [ a F ( N ) + c F ( N − 1 ) b F ( N ) + d F ( N − 1 ) ] = [ F ( N + 1 ) F ( N ) ] \begin{bmatrix} F(N) & F(N-1) \end{bmatrix} × \begin{bmatrix} a & b \\ c & d \end{bmatrix}= \begin{bmatrix} aF(N) + cF(N-1) & bF(N) + dF(N-1) \end{bmatrix}= \begin{bmatrix} F(N+1) & F(N) \end{bmatrix} [F(N)F(N1)]×[acbd]=[aF(N)+cF(N1)bF(N)+dF(N1)]=[F(N+1)F(N)]
也就是
a F ( N ) + c F ( N − 1 ) = F ( N + 1 ) aF(N) + cF(N-1) = F(N+1) aF(N)+cF(N1)=F(N+1)
b F ( N ) + d F ( N − 1 ) = F ( N ) bF(N) + dF(N-1) = F(N) bF(N)+dF(N1)=F(N)
很容易得出
M = [ 1 1 1 0 ] M = \begin{bmatrix} 1 & 1 \\ 1 & 0 \end{bmatrix} M=[1110]

[ F ( N ) F ( N − 1 ) ] × [ 1 1 1 0 ] = [ F ( N + 1 ) F ( N ) ] \begin{bmatrix} F(N) & F(N-1) \end{bmatrix} × \begin{bmatrix} 1 & 1 \\ 1 & 0 \end{bmatrix}= \begin{bmatrix} F(N+1) & F(N) \end{bmatrix} [F(N)F(N1)]×[1110]=[F(N+1)F(N)]
根据数学归纳法可得
[ F ( N + 1 ) F ( N ) ] = [ F ( 1 ) F ( 0 ) ] × [ 1 1 1 0 ] n \begin{bmatrix} F(N+1) & F(N) \end{bmatrix}= \begin{bmatrix} F(1) & F(0) \end{bmatrix} × \begin{bmatrix} 1 & 1 \\ 1 & 0 \end{bmatrix} ^n [F(N+1)F(N)]=[F(1)F(0)]×[1110]n
我们只需要定义出矩阵乘法,配合前面的快速幂,即可实现在 O ( l o g N ) O(logN) O(logN)时间复杂度内解出斐波那契数列。

// 自定义的矩阵类
public class MyMatrix
{
    private int[,] _matrix;
    public int RowNum { get; }
    public int ColNum { get; }

    public int this[int row, int col]
    {
        get => _matrix[row, col];
        set => _matrix[row, col] = value;
    }
    public MyMatrix(int rowNum,int colNum)
    {
        _matrix = new int[rowNum, colNum];
        RowNum = rowNum;
        ColNum = colNum;
    }
	// 矩阵乘法
    public MyMatrix Multiply(MyMatrix b)
    {
        if (ColNum != b.RowNum)
            throw new Exception("矩阵1的列数与矩阵2的行数需相同");
        MyMatrix newMatrix = new MyMatrix(RowNum, b.ColNum);
        for (int i = 0; i < RowNum; i++)
        {
            for (int j = 0; j < ColNum; j++)
            {
                for (int k = 0; k < b.ColNum; k++)
                {
                	// 根据题目要求,结果需要对1000000007取余
                    newMatrix[i, k] = (int)(((long)this[i, j] * b[j, k] + newMatrix[i, k])%1000000007);
                }
            }
        }
        return newMatrix;
    }
}
public class Solution {
	// 程序入口
    public int Fib(int n)
    {
        if (n == 0)
            return 0;
        if (n == 1)
            return 1;
        // 定义变换矩阵
        MyMatrix a = new MyMatrix(2, 2)
        {
            [0, 0] = 1,
            [0, 1] = 1,
            [1, 0] = 1,
            [1, 1] = 0
        };
        MyMatrix res = Pow(a, n);
        // 因为[f(1) f(0)] = [1,0],无需计算直接得出f(n)
        return res[0, 1];
    }

    private MyMatrix Pow(MyMatrix a, int n)
    {
        // 单位矩阵,相当于1
        MyMatrix res = new MyMatrix(2, 2)
        {
            [0, 0] = 1,
            [0, 1] = 0,
            [1, 0] = 0,
            [1, 1] = 1
        };
        while (n != 0)
        {
            if ((n & 1) == 1)
                res = res.Multiply(a);
            a = a.Multiply(a);
            n >>= 1;
        }
        return res;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值