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

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

 

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

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

三.源代碼。測試

 

正文:spa

 一.回顧調試

  1.1.已知的方法code

  關於求a的n次方,有幾種作法吶?對於初學者來講有兩種。以下所示blog

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

相關文章
相關標籤/搜索