基于矩阵快速幂求广义费氏数列一般项

写文动机

偶然在网路上看到 ([1]) 讨论求费氏数列的做法,便去找了一些资料研究并实作。

矩阵快速幂

矩阵快速幂指的是当我们想求一个矩阵M的高次方时,可透过自乘过程中得到的值加速运算,举例来说当k=65,我们想要求 https://chart.googleapis.com/chart?cht=tx&chl=M%5E%7B65%7D,65次方可拆分成 1+64,而 64=32x2, 32=16x2, ...通过自乘我们可得到: https://chart.googleapis.com/chart?cht=tx&chl=M-%3EM%5E2-%3EM%5E4-%3EM%5E8-%3EM%5E%7B16%7D-%3EM%5E%7B32%7D-%3EM%5E%7B64%7D,因此我们只需要计算这些值即可。

参考程式码

def matrix_fast_power(mat_a: np.array, k) -> np.array:    if k == 1:        return mat_a    if k % 2 == 0:        h = matrix_fast_power(mat_a, k // 2)        return h @ h    else:        h = matrix_fast_power(mat_a, (k - 1) // 2)        return h @ h @ mat_a

广义费氏数列

形如 https://chart.googleapis.com/chart?cht=tx&chl=%24%24H_%7Bn%2B2%7D%3DaH_%7Bn%2B1%7D%2BbH_%7Bn%7D%24%24 , https://chart.googleapis.com/chart?cht=tx&chl=%24%24H_0%2CH_1%3Dp%2Cq%24%24 的数列为费氏数列 ([2])之推广。
而广义费氏数列一般项公式为: https://chart.googleapis.com/chart?cht=tx&chl=%24%24H_n%3D%5Cfrac%7Bq-p%5Cbeta%7D%7B%5Calpha-%5Cbeta%7D%5Calpha%5En-%5Cfrac%7Bq-p%5Calpha%7D%7B%5Calpha-%5Cbeta%7D%5Cbeta%5En%24%24 ,其中 https://chart.googleapis.com/chart?cht=tx&chl=%5Calpha%20%3E%20%5Cbeta 为特徵方程式 https://chart.googleapis.com/chart?cht=tx&chl=x%5E2-ax-b%3D0 之两根,详细推导可见此连结 ([3])。

推导

我们仿效此文 ([4])中的方法,令 https://chart.googleapis.com/chart?cht=tx&chl=u_k%3D%5Cbegin%7Bbmatrix%7D%20H_%7Bk%2B2%7D%20%5C%5C%20H_%7Bk%2B1%7D%20%5Cend%7Bbmatrix%7D%20%5Cquad%2CA%3D%5Cbegin%7Bbmatrix%7D%20a%26b%20%5C%5C%201%260%20%5Cend%7Bbmatrix%7D%5Cquad%2Cu_0%3D%5Cbegin%7Bbmatrix%7D%20q%20%5C%5C%20p%20%5Cend%7Bbmatrix%7D%5Cquad,并设 https://chart.googleapis.com/chart?cht=tx&chl=%20A%3DS%20%5CLambda%20S%5E%7B-1%7D,求解 https://chart.googleapis.com/chart?cht=tx&chl=H_%7Bk%2B2%7D相当于求解 https://chart.googleapis.com/chart?cht=tx&chl=u_k。而 https://chart.googleapis.com/chart?cht=tx&chl=%20u_k%3D%20Au_%7Bk-1%7D%3DA(A_%7Bk-2%7D)%3D%20%5Ccdot%20%5Ccdot%20%5Ccdot%20%3D%20A%5Eku_0%3D%20(S%20%5CLambda%20S%5E%7B-1%7D)%5Eku_0%3D%20S%5CLambda%5EkS%5E%7B-1%7Du_0 ,其中 https://chart.googleapis.com/chart?cht=tx&chl=S%3D%20%5Cbegin%7Bbmatrix%7D%20%5Calpha%20%26%20%5Cbeta%20%5C%5C%201%20%26%201%20%5Cend%7Bbmatrix%7D%5Cquad%2C%5CLambda%3D%5Cbegin%7Bbmatrix%7D%20%5Calpha%20%26%200%20%5C%5C%200%20%26%5Cbeta%20%5Cend%7Bbmatrix%7D%5Cquad,可使用矩阵计算器 ([5])验证,搭配根与係数关係 https://chart.googleapis.com/chart?cht=tx&chl=%5Calpha%20%2B%20%5Cbeta%20%3D%20a%2C%20%5Calpha%5Cbeta%20%3D-b

参考程式码

直接对矩阵A计算:

def gen_fib_3(n: int) -> int:    # set up coefficients    a, b, p, q = Constant.a, Constant.b, Constant.p, Constant.q    if n == 0:        return p    if n == 1:        return q    mat_a = np.array([[a, b],                      [1, 0], ])    u_0 = np.array([[q],                    [p], ])    u_k = matrix_fast_power(mat_a, n - 1) @ u_0    return int(u_k[0])

先将矩阵A做对角化再计算:

def gen_fib_5(n: int) -> int:    # set up coefficients    a, b, p, q = Constant.a, Constant.b, Constant.p, Constant.q    if n == 0:        return p    if n == 1:        return q    u_0 = np.array([[q],                    [p], ])    roots = get_roots_of_c_polynomial(a, b)    alpha, beta = roots[0], roots[1]    mat_s = np.array([[alpha, beta],                      [1, 1], ])    alpha_power_k = num_fast_power(alpha, n - 1)    beta_power_k = num_fast_power(beta, n - 1)    mat_lambda_power_k = np.array([[alpha_power_k, 0],                                   [0, beta_power_k], ])    mat_inv_s = np.linalg.inv(mat_s)    u_k = mat_s @ mat_lambda_power_k @ mat_inv_s @ u_0    return int(np.round(u_k[0]))     def get_roots_of_c_polynomial(a: int, b: int) -> List[float]:    mat_a = np.array([[a, b],                      [1, 0], ])    roots = np.roots(np.poly(mat_a))    # sort in descending order    roots = -np.sort(-roots)    return roots

小结

通过矩阵快速幂求费氏数列第N项之时间複杂度为 O(logN),而将矩阵对角化可大幅减少运算步骤,但需要判定是否可对角化以及求出特徵根。当推广至3项递迴 https://chart.googleapis.com/chart?cht=tx&chl=%20H_%7Bn%2B3%7D%3DaH_%7Bn%2B2%7D%2BbH_%7Bn%2B1%7D%2BcH_n%2CH_0%2CH_1%2CH_2%3Dp%2Cq%2Cr 时,即不能保证总是可对角化,此时可直接对原矩阵A求快速幂。
我在程式实作中 import sympy,numpy 帮助求特徵根以及矩阵运算,过程中有转为浮点数进行运算,需留意求出的解是否有误差。

完整程式码 ([6])在此

参考资料

[1] geeksforgeeks: Program for Fibonacci numbers
[2] 维基百科:费波那契数
[3] 陈建烨: 一般的二阶线性递迴数列
[4] 周志成: 费布纳西数列的表达式
[5] 矩阵计算器
[6] hero0963: 程式码实作


关于作者: 网站小编

码农网专注IT技术教程资源分享平台,学习资源下载网站,58码农网包含计算机技术、网站程序源码下载、编程技术论坛、互联网资源下载等产品服务,提供原创、优质、完整内容的专业码农交流分享平台。

热门文章