咱們知道,對於ap(p>0)的求值,樸素的求冪算法採用遞推的方式,即將底數a累乘指數p次做爲結果。這種算法的時間複雜度爲O(p),當指數p很大(>1e7)時,即使該算法擁有線性時間複雜度,在當前主流的機器上仍須要花大量的運算時間。算法
所以須要對樸素冪算法進行改進,一種高效的方式是分治:當p爲偶數時,直接將ap分爲(ap/2)*(ap/2)兩部分,而每一個ap/2又可繼續作一樣的劃分,直到降爲1次冪;當p爲奇數時,咱們從式子中提出一個a,則剩下的ap-1就變爲了偶指數形式,回到了前述狀況;當指數爲0時,能夠很容易得出結果爲1。最後再將分出的結果合併便可。由此咱們能夠寫出遞歸式的數學表達:函數
分治解法的時間複雜度爲O(log p),這個結果相比線性複雜度而言有了很大提高,例如當指數p=18,446,744,073,709,551,616時,只需計算64次乘法便可得出結果。性能
如何實現這個算法呢?首先會想到遞歸地處理這個問題:注意對於較大的運算結果,應考慮高精度模擬運算,或者考慮本文末尾的模m算法。優化
typedef long long ll; ll fspow(ll a, ll p) { if(p==0) return 1; if(p&1) { ll t = fspow(a, p-1); return t*a; } else { ll t = fspow(a, p/2); return t*t; } }
不少人對遞歸有一種直覺上的抵觸,認爲遞歸效率不高。實際並不老是如此。咱們知道函數調用的時間開銷主要體如今兩個方面:一、調用棧會保存函數的形參、局部變量、返回地址等信息,這須要由高延遲的訪存指令來實現;二、分支指令會形成額外的開銷,例如因爲分支預測失效形成的流水線沖刷等等。加密
但事實是,若數據規模很大時,相比於不當心把本來O(log p)的算法活生生地寫成O(p)的算法形成的浪費而言,這些由系統底層形成的開銷是微不足道的。除非你所面臨的是執行性能很是有限的系統,或者你想經過極限優化來避免被卡常,不然大可沒必要在乎函數調用引發的開銷。固然必須考慮遞歸的空間複雜度。spa
兩行代碼的非遞歸實現3d
遞歸算法的致命缺陷是它會增加地佔用棧空間,這一點是咱們不但願看到的。可否有一種既能保持簡潔優雅,又能擁有常數空間複雜度的快速冪算法呢?code
有的,它就是快速冪算法的非遞歸版本。下面這兩行代碼已經將核心濃縮到了極致:blog
1 for (s=1; p;p/=2,a=a*a) 2 if (p&1) s=s*a;
其中a爲底數,p爲指數,算法結束後,s=ap。遞歸
再來看代碼的原理。思路還是分治,這與咱們以前的討論是一致的,不一樣的是再也不遞歸地求解。
「分」的本質是使指數折半,這裏指數必須爲偶數。當指數折半後,要使冪結果不變,底數必須平方。形式化描述。而指數p/2又可繼續分爲p/4 p/8……1,底數則不斷平方。當指數降爲1時,底數即爲要求的結果。當指數不是偶數時,先拆出一個底數,則剩下的冪就是偶次冪,再按如上方法處理便可。
代碼中涉及一些小技巧:
1. 利用了整除算符/的向下取整特性。當指數p爲奇數時,p/2的結果實質上等同於p-1/2,這就免去了當咱們拆出一個底數時須要把p減一的麻煩。事實上,因爲除數爲2,也能夠寫成p>>=1,本質上利用了二進制除法。
2. 咱們知道%運算符的開銷是很大的,這裏只需判斷p的奇偶性,直接判斷p的二進制最低位便可。
具體應用
冪運算在許多場合都有着重要的運用。例如RSA非對稱加密算法中,明文經過以下公式進行加密:
Ci = Mie mod n
其中Ci爲密文,Mi爲與明文有關的數值,(e, n)爲公鑰對。
而密文經過以下公式解密:
Mi = Cid mod n
其中Mi爲與明文有關的數值,(d, n)爲私鑰對。
能夠看到,不管是加密仍是解密,都要用到冪運算與模運算。這就與咱們上文所述的快速冪運算頗有關聯。
不止RSA,在其它運用中也經常出現類型的形式,它們歸結下來以下面這個式子:
ap mod k
因爲對冪取模,最終結果並不會大於等於k。再看看咱們的快速冪算法,中間結果極可能遠大於k,若是中間結果超出基本數據類型的範圍,還須要經過高精度模擬運算。咱們很快發現,當k可經過基本數據類型表示時,彷佛沒有必要採用高精度算法,上文的快速冪算法還能夠改進。
數論指出,(a*b) mod k = (a mod k)*(b mod k) mod k。即兩個數的乘積再取模,等於兩個數分別取模再相乘,最後取模。利用這個性質,咱們能夠將ap mod k等價變形爲(a mod k)p mod k,同時每次作乘法時,都先利用該性質縮小乘數,再相乘。
上述代碼修改成
1 a %= k; 2 for (s=1; p;p/=2,a=(a*a)%k) 3 if (p&1) s=(s*a)%k;
其中a,p,k,s的含義與上文一致。
這即是快速冪模k算法的非遞歸實現。