快速幂算法及其拓展

来源:互联网 发布:人工智能具体应用实例 编辑:程序博客网 时间:2024/06/07 09:07

摘要

本文讲解了快速幂算法的定义、复杂度证明及两种实现(递归与非递归),以及它的两个重要拓展:快速幂模M算法和矩阵快速幂。其中矩阵快速幂算法是矩阵求幂问题对整数求幂问题的借鉴,实际应用中对于线性递推式求解能起到强大的效率优化。


快速幂算法

问题引入:求an(a,nN+)
朴素算法:令ans初始值为1,乘n次a得到an
朴素算法时间复杂度:O(n)

问题:如果n非常大,比如高达1015,怎么办?
思考:朴素算法哪里可以优化?

朴素算法的特点是,连乘过程中底数始终为a,这很不聪明。考虑下例:
a=2,n=15
我们没有必要乘15次2,注意到15=23+22+21+20,不妨将215拆分为223×222×221×220
然后看一下n的二进制形式:15=(1111)2.为什么要看这个呢?再举一个例子。

a=2,n=6
26=222×221,而6=(110)2

发现什么规律?
观察发现,当n的第i位(从低到高,i>=0)为1时,an的拆解表达式里就要乘上一项a2i.

于是我们有了朴素算法改进的思路:逐位判断幂次n的二进制位是否为1,若是,给答案乘上一个a2i.
改进后的算法的伪代码描述如下:

a, n, ans;ans=1;while n>0    a = a*a;    if n%2        ans = ans*a;    n = n/2;

这个算法的时间复杂度是多少呢?很显然,它取决于n的二进制形式有多少位,因此T(n)=log2(n)=O(logn).这就是快速幂算法。

其实快速幂算法还可以递归实现,因为:
当n为偶数时,an=(an/2)2
当n为奇数时,an=(an/2)2×a
边界条件:当n=1,答案为a
C语言描述如下:

int quick_power(int a, int n){    if(n == 1) return a;    int x = quick_power(a, n/2);    long long ans = (long long)x*x;    if(n%2) ans *= a;    return (int)ans;}

代码中加入了防溢出处理,用快速幂算法的时候比较容易犯的一个错误就是忘了考虑溢出,因此在使用的时候要看清楚数据范围,估算一下答案上界。另外,快速幂算法不推荐用递归实现,因为非递归版本不但代码也很简洁,而且效率还更优。

拓展一:快速幂模M算法

有时候所求幂的结果可能很大,于是问题要求对结果模上一个数M。我们只需要在原来算法的基础上运用一下模运算的性质即可。所谓模运算性质是指以下两条:
ni=1ai mod M=(ni=1ai mod M)mod M
ni=1ai mod M=(ni=1ai mod M)mod M
算法非递归实现的伪代码描述为

a, n, ans, M;ans=1;while n>0    a = a*a % M;    if n%2        ans = ans*a % M;    n = n/2;

拓展二:矩阵快速幂算法

快速幂算法解决的是整数求幂的问题,而矩阵快速幂解决的是矩阵求幂问题,两者没有本质的区别。如果用C++实现,我们只要定义一个矩阵类,然后重载一下乘法运算符,原先的快速幂算法几乎不需要改变。
矩阵快速幂常常用于线性递推式的加速。以下仅举一例。

快速求斐波那契数列第n项

对于这个问题,普通求法的复杂度是O(n),现在我们用矩阵快速幂将它优化到O(logn).
首先,将递推式f(n)=f(n1)+f(n2)改写成矩阵形式

[f(n)f(n2)f(n1)f(n3)]=[f(n1)f(n3)f(n2)f(n4)][1110]

进而得到
[f(n)f(n2)f(n1)f(n3)]=[f(4)f(2)f(3)f(1)][1110]n4

接下来,用矩阵加速算法求出[1110]n4,再做一次矩阵乘法,所得矩阵的(0,0)元素就是最终结果。

对于更一般的线性递推式,构造其加速矩阵的方式超出了本文的论述范围,在此省略。

0 0
原创粉丝点击