快速幂算法

Posted Cukor丘克

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了快速幂算法相关的知识,希望对你有一定的参考价值。

快速幂算法

设计一个算法计算 x n x^n xn的值。

根据定义最常见也最能瞬间想到的是如下的算法:

// 递归写法
public int pow1(int x, int n) 
  if (n == 0) return 1;
  if (n == 1) return x;
  return x * pow1(x, n - 1);

// 循环写法
public int pow2(int x, int n) 
  int y = 1;
  while (n) 
    y *= x;
    n--;
  
  return y;

但上面的算法的时间复杂度是 O ( n ) O(n) O(n)

下面采用快速幂算法来解决这个问题。

在解决它之前先来看一下原理: x n = x n − a x a x^n=x^n-ax^a xn=xnaxa

所以我们可以对本身要求的 x n x^n xn对半分来求,只求一半的数,然后乘以自己本身就可以达到 x n x^n xn

但是会出现的情况就是,对半分的时候会出现小数的情况,所以一定要分奇数和偶数的情况。

如果n分半了之后是偶数,那就直接对半分,如果是奇数则在对半分之后还要乘以一个x。

所以可以有下面的规律:

f(x, n) = 
  f(x, n/2)*f(x, n/2),    // 当n为偶数
  x*f(x, n/2)*f(x, n/2)   // 当n为奇数

所以得出快速幂算法1:

public int qPow1(int x, int n) 
  if (n == 0) return 1;
  if (n == 1) return x;
  if (n % 2 == 1) return x*f(x, n/2)*f(x, n/2);
  return f(x, n/2)*f(x, n/2);

但是上面的算法明显没有任何增进,因为f(x, n/2)要算两次,那和之前的 O ( n ) O(n) O(n)的算法没什么区别。所以使用一个中间变量去接受一下,就可以提高算法效率。

public int qPow1(int x, int n) 
  if (n == 0) return 1;
  if (n == 1) return x;
  int t = f(x, n/2)
  if (n % 2 == 1) return x * t * t;
  return t * t;

上面的快速幂算法还是比较好理解的,下面的快速幂算法就比较的炫技了我觉得,但是也就那样(原理还是上面的,只是不是对半分而已,而是根据进制数来分)。

下面采用二进制数来分。

假设我们要计算的是 x 10 x^10 x10,那么10的二进制数是1010,所以有如下公式及变换: x 10 = x ( 10 ) 10 = x ( 1010 ) 2 = x 1 ∗ 2 3 + 0 ∗ 2 2 + 1 ∗ 2 1 + 0 ∗ 2 0 = x 8 + 2 = x 8 x 2 x^10=x^(10)_10=x^(1010)_2=x^1*2^3+0*2^2+1*2^1+0*2^0=x^8+2=x^8x^2 x10=x(10)10=x(1010)2=x123+022+121+020=x8+2=x8x2

x 10 = x 8 x 2 x^10=x^8x^2 x10=x8x2

和上面第一种快速幂的算法类似,只不过上一种采用的分法是: x 10 = x 5 x 5 x^10=x^5x^5 x10=x5x5

那么不管怎样分,最后肯定会被分到1,因为 x 0 = 1 x^0=1 x0=1,其实上面的分法都隐藏了一个 x 0 x^0 x0,即: x 10 = x 8 x 2 x 0 x^10=x^8x^2x^0 x10=x8x2x0

所以状态是怎么转移的,即每一次迭代都是怎样变化的。初始化 t = x 2 0 = x 1 = x t=x^2^0=x^1=x t=x20=x1=x,那么下一代的变化是 x 2 1 x^2^1 x21,它是由 x 2 0 ∗ 2 x^2^0*2 x202变化而来,因为采用的是二进制。所以指数部分要想从 2 0 2^0 20变换到 2 1 2^1 21就需要乘以一个2.那也就是说, x x x变到 x 2 x^2 x2.那么迭代变化过程就是 t = t ∗ t t=t*t t=tt.

x 2 0 ∗ 2 = ( x 2 0 ) 2 x^2^0*2=(x^2^0)^2 x202=(x20)2

所以得到第二种快速幂算法代码:

public int qPow2(int x, int n) 
  int y = 1;
  int t = x;
  while (n > 0) 
    switch (n % 2) 
      case 1: y = y * t;  // 这里不要写break
      case 0: t = t * t;
    
    n = n / 2;
  
  return y;

那么这个采用二进制的方法分,当然也有三进制的,四进制的,五进制的等等。十六进制就不要搞了,因为不是进制越高就越快。

通过我对上面的二进制写法的快速幂就可以看出来我还会有其他进制的写法。那么下面就来看一下三进制的写法,然后四进制的就顺其自然就明白了。

那么三进制的推导过程也是和二进制的推导过程是类似的。假设计算的是 x 10 x^10 x10. x 10 = x ( 10 ) 10 = x ( 101 ) 3 = x 1 ∗ 3 2 + 0 + 3 1 + 1 ∗ 3 0 = x 9 x x^10=x^(10)_10=x^(101)_3=x^1*3^2+0+3^1+1*3^0=x^9x x10=x(10)10=x(101)3=x132+0+31+130=x9x

所以从二进制的分法和三进制的分法可以看出,不管怎么分都是可以合起来达到10.只要能达到10的说明采用什么进制分法都是可以的。但并不是说采用的进制越高就越好。

那么初始化 t = x 3 0 = x t=x^3^0=x t=x30=x,那么下一代的变化是 x 3 1 x^3^1 x31,它是由 x 3 0 ∗ 3 x^3^0*3 x303变化而来,因为采用的是三进制。所以指数部分要想从 3 0 3^0 30变换到 3 1 3^1 31就需要乘以一个3.那也就是说,

转C语言快速幂取模算法小结

(转自:http://www.jb51.net/article/54947.htm)

本文实例汇总了C语言实现的快速幂取模算法,是比较常见的算法。分享给大家供大家参考之用。具体如下:

首先,所谓的快速幂,实际上是快速幂取模的缩写,简单的说,就是快速的求一个幂式的模(余)。在程序设计过程中,经常要去求一些大数对于某个数的余数,为了得到更快、计算范围更大的算法,产生了快速幂取模算法。我们先从简单的例子入手:求abmodc

算法1.直接设计这个算法:

int ans = 1;
for(int i = 1;i<=b;i++)
{
  ans = ans * a;
}
ans = ans % c;

缺点:这个算法存在着明显的问题,如果a和b过大,很容易就会溢出。

我们先来看看第一个改进方案:在讲这个方案之前,要先看这样一个公式:ab mod c = (a mod c)c mod c

于是不用思考的进行了改进:

算法2.改进算法:

int ans = 1;
a = a % c; //加上这一句
for(int i = 1;i<=b;i++)
{
  ans = ans * a;
}
ans = ans % c;

读者应该可以想到,既然某个因子取余之后相乘再取余保持余数不变,那么新算得的ans也可以进行取余,所以得到比较良好的改进版本。

算法3.进一步改进算法:

int ans = 1;
a = a % c; //加上这一句
for(int i = 1;i<=b;i++)
{
  ans = (ans * a) % c;//这里再取了一次余
}
ans = ans % c;

这个算法在时间复杂度上没有改进,仍为O(b),不过已经好很多的,但是在c过大的条件下,还是很有可能超时,所以,我们推出以下的快速幂算法。

算法4.快速幂算法:

快速幂算法依赖于以下明显的公式:

技术分享

int PowerMod(int a, int b, int c)
{
  int ans = 1;
  a = a % c;
  while(b>0) {
    if(b % 2 = = 1)
    ans = (ans * a) % c;
    b = b/2;
    a = (a * a) % c;
  }
  return ans;
}

本算法的时间复杂度为O(logb),能在几乎所有的程序设计(竞赛)过程中通过,是目前最常用的算法之一。

相信本文所述对大家算法设计的学习有一定的借鉴价值。

 

以上是关于快速幂算法的主要内容,如果未能解决你的问题,请参考以下文章

快速幂算法

每日算法快速幂

打工人必会的快速幂算法详解

算法初步:快速乘,快速幂,矩阵快速幂

整数快速乘法/快速幂+矩阵快速幂+Strassen算法 (转)

转C语言快速幂取模算法小结