大整數冪求模問題

來源:互聯網
上載者:User

   
      一、 問題描述:

              計算 (a^power) % m , 其中power 是非負的大整數, a, m 為大於1 的整數。

      二、 問題分析:  

        很顯然, 由於 power 是大整數,因此,必須考慮到冪計算的溢出問題。怎麼避免溢出呢? 可以通過降低冪次、逐次模數來實現。一個自然的想法是,將power 分成兩個整數之和, power = n1 + n2,則 a^power = (a^n1) * (a^n2) .    通常採用二分法, n1 = n2 或 |n1-n2| = 1 。這就涉及到 (a * b) % m 的計算 。

   不難證明:   (a * b) % m = ((a % m) * (b % m)) % m。  

   證明如下:   設 a = pm + r1, b = qm + r2 , 則 r1 = a % m, r2 = b % m ,  

                          則  (a * b) % m = (r1 * r2) % m = ((a % m) * (b % m)) % m.   證畢。

   特別地,當 a = b 時, (a^2) % m = ((a % m)^2) % m ;   這是一個簡單卻又關鍵性的結論。

三、 演算法設計:

        演算法1: 分治策略:

(1)若power 為奇數: 令 power = 2k+1, k 為非負整數  , 則

                   a^(2k+1) = (a^k)^2 *a ; a^(2k+1) % m = ((a^k % m)^2 % m * a) % m
  (2)   若power 為偶數:    令 power = 2k, k為非負整數, 則

                   a^(2k) = (a^k)^2 ; a^(2k) % m = (a^k % m)^2 % m
  (3)   若power == 1 : 返回 a % m ; 若 power == 0: 返回 1 % m。

       據以上(1)/(2)/(3) 條, 即可設計出相應的遞迴求解程式。時間複雜度為T(n) = T(n/2) + C = O(logn) ,空間複雜度為 O(1), 不足之處在於有一定的遞迴調用開銷。

        演算法2: 整數的二進位分解

 將大整數 power 按照二進位進行分解:

            power = a[N] * 2^N + a[N-1] * 2^(N-1) + … + a[1] * 2 + a[0]

其中: a[i] 取值為 0 或 1 ( i=0,1,.., N),則

           a^power = a^(a[N] * 2^N) * a^(a[N-1] * 2^(N-1)) * … * a^(a[1] * 2) * a^a[0]
 很顯然:  

          (1) 若 a[i] = 0, 則 a[i] * 2^i = 0 , a^(a[i]*2^i) = 1, 該乘積項可以消去;

          (2) 若 a[i] = 1, 則 a[i] * 2^i = 2^i , a^(2^i) % m = (a^(2^(i-1)) % m)^2 % m.  

                令 temp = a^(2^(i-1)) % m , 則 a^(2^i) % m = (temp * temp) % m。

比如,  power = 26 = 16 + 8 + 2 = (11010)2, 則 a^26 = a^(2^4 + 2^3 + 2^1);  

計算 a^power 實際上是計算 power 的二進位表示中所有位為1對應的乘積項。

                (a^26) % m = ((a^16 %m) * (a^8 %m) * (a^2 % m)) %m

而, a^8 % m = ((a^4 %m) × (a^4%m)) % m  是可以用動態規劃法逐次求解的。 簡單起見, 將 (a^i) % m 稱為 “模數乘積項”。

演算法描述:

令 temp = a^(2^i) % m , i 是 power 的當前二進位位所對應的位置,

                                        temp 表示 power 的當前二進位位所對應的(模數)乘積項

STEP1:   初始化 temp 為 a % m  ,  result = 1;

STEP2:   對 power 進 行二進位分解。 若 power >=1 , 則進行模2運算:否則轉至 STEP3

 [1]  若餘數為1, 則該位置上的二進位為1, 乘積中需要加入此時的 temp 項 :  result = (result * temp) % m; 

           下一個二進位位對應的乘積項為 temp = (temp * temp) % m 

 [2]  若餘數為0, 則該位置上的二進位為0,乘積中不需要加入 temp 項, result 保持不變, 

           下一個二進位位對應的乘積項為 temp = (temp * temp) % m 

STEP3: 所有的二進位位對應的乘積項都已計算,演算法結束。


比如, result = (3^26) % 5 的計算過程如下:26 = (11010)2 ; 

(1)初始化:  temp = 3^1 % 5 = 3;, result = 1 ;  

  (2)   檢測 26 的最低位二進位為0,  則 不加入乘積項,result = 1,   temp =(3^2) % 5 = (temp * temp) % 5 = 4

  (3)   檢測 26 的次低位二進位為1, 則 加入乘積項, result = (result * temp) % 3 = 4 , temp = (3^4) % 5 = (4*4) % 5 = 1;

  (4)   檢測 26 的次低位二進位為0, 則 不加入乘積項, result = 4, temp = (3^8) % 5 = (1*1) % 5 = 1; 

  (5)   檢測 26 的次低位二進位為1, 則 加入乘積項, result = (result * temp) % 5 = 4, temp = (3^16) % 5 = 1;

  (6)   檢測 26 的次低位二進位為1, 則 加入乘積項, result = (result * temp) % 5 = 4, temp = (3^32) % 5 = 1.

   由於所有位都檢測完畢, 故 3^26 % 5 = 4.  由上可知, 

   3^26 % 5 = ((3^16) % 5)) * ((3^8) % 5) * ((3^2) % 5) % 5.  其中 3^16 % 5, 3^8 % 5, 3^2 % 5 是通過動態規劃法逐漸求得的。

 

完整的程式如下:  

程式在 Ubuntu10.10 gcc4.4.5 環境下編譯運行通過。
  $ gcc -g -Wall bintmode.c runtime.c -o bintmode # 編譯串連
  $ bintmode or gdb bintmode # 運行

 

/* * bintmode.c :  此程式計算 (a^power) % mod. power 是大整數 * 基本公式: (a*b) mod m = ((a mod m) * (b mod m)) mod m */#include <stdio.h>#include <assert.h>int modMRec(int, int, int);int modMDyn(int, int ,int);void testModMRec(int);void testModMDyn(int);void testValid(int (*fun)(int a, int power, int mode));void testInvalid(int (*fun)(int a, int power, int mode));int main(){   printf(" ******** 使用遞迴技術求解: ******** \n");   testValid(modMRec);   /* testInvalid(modMRec);  */    defRuntime(testModMRec);   printf(" ******** 使用二進位分解技術求解: ******** \n");   testValid(modMDyn);   /* testInvalid(modMDyn);  */    defRuntime(testModMDyn);   return 0;}/* * modMRec: 遞迴求解 (a^power) % mod , power 是個大整數 */int modMRec(int a, int power, int mod){   assert(a>=1 && power >=0 && mod >=1);   if (power == 0) {       return 1 % mod;   }   if (power == 1) {       return a % mod;   }   if (power % 2 != 0) {       int temp = modMRec(a, power/2, mod);       return  (temp * temp * a) % mod;   }    else {       int temp = modMRec(a, power/2, mod);       return  (temp * temp) % mod;   }}/* * modMDyn: 使用二進位分解技術求解 (a^power) % mod , power 是個大整數 */int modMDyn(int a, int power, int mod){   assert(a>=1 && power >=0 && mod >=1);   int result = 1;   int temp = a % mod;   while (power >= 1) {      if (power % 2 != 0) {         result = (result * temp) % mod;         }      temp = (temp * temp) % mod;      power /= 2;      }   return result;}void testValid(int (*fun)(int a, int power, int mode)){   int base[5] = {2,3,5,7,11};   int i,k;   for (i=0; i < 5; i++) {     for (k = 0; k < 20; k++) {        printf("%d ^ %d mod %d = %d .\n", base[i], k, 10, (*fun)(base[i], k, 10));     }       }}void testInvalid(int (*fun)(int a, int power, int mode)){    printf("0 ^ 3 mod 5");   (*fun)(0, 3, 5);     printf("3 ^ -1 mod 5");   (*fun)(3, -1, 5);   printf("3 ^ 5 mod 0");   (*fun)(3, 5, 0);}void testModMRec(int n){   modMRec(2, n, 10);}  void testModMDyn(int n){   modMDyn(2, n , 10);}

#include <stdio.h>#include <time.h>#define MAX_SCALE 10long defScale[MAX_SCALE] = {1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000};/* runtime: 測量 fun 指向的函數的已耗用時間 * fun: 指向測試函數的指標 ; * scale:  問題規模數組  */void runtime(void (*fun)(long scale), long* scale, int len);void defRuntime(void (*fun)(long scale));void runtime(void (*fun)(long scale), long *scale, int len){   int i;   clock_t start, end;   for (i = 0; i < len; i++) {      start = clock();      (*fun)(scale[i]);      end = clock();      printf("scale: %12ld\t run time: %8.4f (ms). \n", scale[i], ((double)(end-start)) * 1000 / CLOCKS_PER_SEC);   }}void defRuntime(void (*fun)(long scale)){   runtime(fun, defScale, MAX_SCALE);}

 

四、總結: 關於科學計算的演算法
1. 往往要先查閱相關資料,瞭解相應的數學性質及結論,並對問題進行化簡;例如本例中使用 (a*b)%m = ((a%m)*(b%m))%m 的模性質,避免了乘法溢出的問題。
2. 可以首選分治法和二進位分解法。分治法將所要求解的數值規模減半,而二進位分解則從數值的一個特別角度來尋求問題的解答。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.