1. 程式人生 > >模冪運算的幾種解決方法

模冪運算的幾種解決方法

【問題】
計算a**b%c的值。
其中,"**"代表冪(Python中就是這樣表示的);"%"代表取模運算。


【分析】
首先由模運算的性質,可以得到下面的公式:
(a*b) % c = (a%c) * b % c        【公式一】
將【公式一】繼續展開,可得下面的公式:
(a*b) % c = (a%c) * b % c = (b%c) * (a%c) % c = (a%c) * (b%c) % c        【公式二】

下面是幾種解決方法:


【方法1】利用公式一,使用遞迴方法計算。

不難看出此方法的缺點是當b越大的時候需要遞迴的次數就越多,因此就可能會發生stack overflow的錯誤。所以,此方法非常簡單但只適用於b比較小的情況。VS2008下測試,當遞迴到4624次時發生stack overflow。

【方法2】 利用公式二,使用遞迴方法計算。
想辦法在方法1的基礎上減少遞迴次數,發現利用公式二可以做到。
(a*b) % c =  (a%c) * (b%c) % c        【公式二】
當b是奇數的時候,f(b) = f(b-1) * (a % c) % c = a * f(b-1) % c
當b是偶數的時候,f(b) = f(b/2) * f(b/2) % c

VS2008測試:a=3; b=2147483647;(取int型的最大值) c=1000時,需要約225s可計算出結果為787。
此方法減少了遞迴次數,在所能表示的型別裡可以防止棧溢位的問題(下面可推證),但是發現運算效率還是太慢,對程式碼進行檢查可以發現問題出在下面這行程式碼中:
fun(a, b/2, c)*fun(a, b/2, c)%c;
這種寫法會導致遞迴呼叫函式的次數可能達到2*lb(b),雖然遞迴深度要遠小於方法1,但遞迴次數要遠遠大於方法1(可以設定一個區域性靜態變數顯示),所以效率很低。解決方法可以先用一個臨時變數來儲存函式呼叫的返回值,優化方法如下:

用上述同樣的資料測試,發現計算所用時間幾乎為0.00s,效率得到很大提高。

思考:雖然這種方法減少了遞迴次數,但是當b特別大的時候是否還會出現stack overflow的情況。
測試:將b改為__int64或long long型別,看是否會發生stack overflow並計算時間。
可以粗略地分析出:在32位平臺上用這種方法可以計算出b最大為2^4624時的結果而不會發生棧溢位。

測試:a=3; b=9223372036854775807; c=1000; 輸出187,用時幾乎為0.00s。

【方法3】利用公式一,使用遞推方法(非遞迴)計算。

測試:a=3; b=2147483647;(取int型的最大值) c=1000時,用時約37.02s可計算出結果為787。
因為此方法需要迴圈b次,時間複雜度為O(b),效率比較低,但是優點是不會發生stack overflow。

【方法4】利用公式二,使用遞推方法(非遞迴)計算。

測試:a=3; b=2147483647;(取int型的最大值) c=1000時,用時約0.00s可計算出結果為787。此方法最好的時間複雜度為O(lb(b)),且不會發生stack overflow。

【方法5】與方法4等價的另一種形式。
不對b進行減1或除以2,而將b表示為二進位制數,通過判斷二進位制位上是0還是1來計算。這種方法的時間複雜度最小,即O(nbit),n是b的二進位制表示的位數。比如:
b==8(dec.)==1000(bin.),用方法4需要迴圈2+1+1+0=4次;用方法3也需要迴圈4次。
b==7(dec.)==0111(bin.),用方法4需要迴圈2+2+1=5次;用方法3需要迴圈4次。

使用陣列實現

使用bitset容器實現

參考