快速冪原理解析與其他方法回顧


快速冪原理解析與其他方法回顧

 

目錄:
一.回顧朴素法與使用庫函數,分析利弊。

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

三.源代碼。

 

正文:

 一.回顧

  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 *=c urr; 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;}
 8     return ;
 9 }

  步驟與上面一模一樣。

  由此,快速冪便完成了。 

  箜瑟_qi 2016.02.08


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM