快速冪原理解析與其餘方法回顧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