快速幂原理解析与其他方法回顾

快速幂原理解析与其他方法回顾

 

目录:
一.回顾朴素法与使用库函数,分析利弊。

二.引例:指数的分解,即快速幂的原理。

三.源代码。

 

正文:

 一.回顾

  1.1.已知的方法

  关于求a的n次方,有几种做法呐?对于初学者来说有两种。如下所示

1 void poww1(int a, int b){
2     long long ans = 1;
3     for(register int i = 0; i < b; i++)
4         ans *= a;
5     return ans;
6 }
1  #include <cmath>//poww2
2     ans = (long long) pow(a, b);//调用cmath库的pow函数,但是注意返回值不是longlong/int型,是double型

  观察poww1,一个明显的问题便是它的时间复杂度比较高,是O(n)的复杂度,即n次方需要乘n次才可得到结果,较慢。

  观察poww2,更加明显的问题在于其函数的返回值是个浮点数,这是一个定时炸弹,有可能使用pow(10, 4)时得到9999的答案,让你调试后欲哭无泪。

 

  1.2. 测试对比

  测试程序如下:

 1 #include <iostream>
 2 #include <cstdio>
 3 #include <cmath>
 4 using namespace std;
 5 int main(){   
 6      int n = 0;  
 7      for(int i = 0; i <= 4; i++)  
 8          n += (i+1)*(int)pow(10, 5-i);  
 9      cout << n << endl;  
10      return 0;  
11 }

 

  使用Dev-C++ 5.4.0 与Smart c++ 2013分别运行,得到测试数据:

  由于精度问题,结果不同。

  有人会说:"不要强转成int啊,用long long 就不会丢失精度啦。"

  那么将循环改成——

1 for(int i = 0; i <= 4; i++)  
2     n += kong[i]*(long long)pow(10, 5-i); 

  测试结果如下:

  根本就没有变哎!

 

  1.3.对比两种方法运行的时间与准确性

  利用循环次数更大的测试程序如下:

1     int i;  
2     long long n = 0;  
3     for(i = 0; i <= 10; i++){
4         n += (i+3)*(long long)pow(5, 11-i);  
5     }
6     cout << n << endl;  
 1 long long poww(int a, int b){
 2     long long ans = 1;
 3     for(int i = 0; i < b; i++)
 4         ans *= a;
 5     return ans;
 6 }
 7 int main(){  
 8     int i;  
 9     long long n = 0;  
10     for(i = 0; i <= 10; i++){
11         n += (i+3)*(long long)pow(5, 11-i);  
12     }
13     cout << n << endl;  
14     return 0;  
15 }

  运行结果如下:

  (上下分别是方法二与方法一)

 

  1.4. 结论

  两者优劣一目了然:方法一相对较快且保证在不超范围内一定正确,不过要多打几行代码;方法二短,但是坑。

  若选择以上两种方法,自己选吧。。。

  那么我们的目标便是继承方法一的优点,改良其缺点:即保持准确性的情况下降低时间复杂度。

 

 二.快速幂引理

  2.1.先看效果:

  答案一致,时间不到方法一的1/2,并且在当次方数极大的时候,时间会远小于方法一,原因便是因为其时间复杂度为O(logn)(logn通常指log2n)

 

  2.2引理的数学形式

  引理:∀ 取X∈N*皆可被分解为形如X = 2^a+2^b+...+2^k(a != b !=  ……  != k);

  设2^0+2^1+2^2+…+2^n = m;……(1)式

  2^1+2^2+2^3+…+2^(n+1) = 2*m;……(2)式

  (2)式-(1) 式= (1)式 = 2^(n+1)-2^0 = 2^(n+1)-1……(*)

  当X != 2^m时

  ∵int X > 0;

  ∴ X = 2^m - 1 - k(int k >= 0);

  if(x%2 == 1)  k %2 == 0;

  else k%2 == 1;

  ∵2^m - 1 = 2^0 + 2^1 + … +2^(m-1);

  ∴若原式成立,则k可分解为2某些次方的和。

  当k%2 == 1时,必有2^0,减掉,k为偶数;

  那么现在X 就缩小为了正偶数。

  观察2 4 8 16 ……我们会发现一点,即第i个数后面的数都2^i有作为因数,而它前面的数因数中则没有2^i,通过这个便可以确定k的分解。

  利用递归的思想……X缩小为4的倍数,8的倍数……

 

  2.3例子:

  举个例子 : k = 17的分解,按照以下步骤。

  int cnt = 1;

  while (k){

   if(k%(2^cnt) != 0) {

    k得到新的分解 : 2^(cnt-1);

    k -= 2^(cnt-1);

    //此时k%2(cnt) == 0

    cnt ++;

  }

 }

  17%2 == 1; => 17 = 2^0 + m; 17-1 = 16;

  16%2 == 0; => 16/2 = 8 

  ……

  1%2 == 1; =>17 = 2^0 +2^4;

   1/2 == 0 终止循环; 

  那么这种东东对做幂运算有什么用呢?

  答案便是 —— 将指数按照此方法分解

  例如:求6^11

  我们已知 6^0 == 1 ,a ==  6^1 == 6;

  11 == 2^0 + 2^1 + 2^3;

  所以 6^11 == 6^1 * 6^2 * 6^8;

  我们还知道 a^2 == 36;

  那么我们将上述分解指数的步骤加入乘法以获得最终解:

  curr = 6;ans = 1;

  11 % 2 == 1 => ans *= 6^0;curr = 6^2

  5 %2 == 1 => ans *= 6^2;curr = 6^4;

  2%2 == 0 =>curr = 6^8;

  1%2 == 0 ans *=curr; ans = 6^11;

  一共用了ceil(log(2)11) = 4 步

  每次curr自我平方一次,以准备下一次的使用。而当k%2 == 1 时,就意味着需要使用curr将指数进行分解。

 

  2.4.源代码 

  那么给出源代码:

 1 void poww(int a, int b){
 2     long long curr = a,ans = 1, last;
 3     while (b){
 4         if(b%2)    ans *= curr;
 5         curr *= curr;
 6         b /= 2;
 7     }
 8     return ;
 9 }

  步骤与上面一模一样。

  由此,快速幂便完成了。 

  箜瑟_qi 2016.02.08

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值