1. 程式人生 > >快速冪取模運算

快速冪取模運算

1.大數模冪運算的缺陷:

快速冪取模演算法的引入是從大數的小數取模的樸素演算法的侷限性所提出的,在樸素的方法中我們計算一個數比如5^1003%31是非常消耗我們的計算資源的,在整個計算過程中最麻煩的就是我們的5^1003這個過程缺點1:在我們在之後計算指數的過程中,計算的數字不都拿得增大,非常的佔用我們的計算資源(主要是時間,還有空間)缺點2:我們計算的中間過程數字大的恐怖,我們現有的計算機是沒有辦法記錄這麼長的資料的,所以說我們必須要想一個更加高效的方法來解決這個問題

2.快速冪的引入:

我們首先從優化的過程開始一步一步優化我們的模冪演算法

1.樸素模冪運算過程:

  1. #define ans=1
  2. for(int i=1;i<=b;i++)  
  3. {  
  4.     ans*=a;  
  5. }  
根據我們上面說的,這種演算法是非常的無法容忍的,我們在計算的過程中出現的兩個缺點在這裡都有體現在這裡我們如果要做優化的話,我肥就是每個過程中都加一次模運算,但是我們首先要記住模運算是非常的消耗記憶體資源的,在計算的次數非常的大的時候,我們是沒有辦法忍受這種時間耗費的

2.快速冪引入:

在講解快速冪取模演算法之前,我們先將幾個必備的知識1.對於取模運算:
  1. (a*b)%c=(a%c)*(b%c)%c  
這個是成立的:也是我們實現快速冪的基礎之後我們來看看快速冪的核心本質我通過離散課上的學習,將快速冪的本質差不多理解了一下,感覺還是很深刻的在這裡,我們對指數懂了一些手腳,核心思想在於將大數的冪運算拆解成了相對應的乘法運算,利用上面的式子,始終將我們的運算的資料量控制在c的範圍以下,這樣我們可以客服樸素的演算法的缺點二,我們將計算的資料量壓縮了很大一部分,當指數非常大的時候這個優化是更加顯著的,我們用Python來做一個實驗來看看就知道我們優化的效率有多高了
  1. from
     time import *  
  2. def orginal_algorithm(a,b,c):  #a^b%c
  3.     ans=1
  4.     a=a%c  #預處理,防止出現a比c大的情況
  5.     for i in range(b):  
  6.         ans=(ans*a)%c  
  7.     return ans  
  8. def quick_algorithm(a,b,c):  
  9.     a=a%c  
  10.     ans=1
  11.     #這裡我們不需要考慮b<0,因為分數沒有取模運算
  12.     while b!=0:  
  13.         if b&1:  
  14.             ans=(ans*a)%c  
  15.         b>>=1
  16.         a=(a*a)%c  
  17.     return ans  
  18. time=clock()  
  19. a=eval(input("底數:"))  
  20. b=eval(input("指數:"))  
  21. c=eval(input("模:"))  
  22. print("樸素演算法結果%d"%(orginal_algorithm(a,b,c)))  
  23. print("樸素演算法耗時:%f"%(clock()-time))  
  24. time=clock()  
  25. print("快速冪演算法結果%d"%(quick_algorithm(a,b,c)))  
  26. print("快速冪演算法耗時:%f"%(clock()-time))  
實驗結果:
  1. 底數:5  
  2. 指數:1003  
  3. 模:12  
  4. 樸素演算法結果5  
  5. 樸素演算法耗時:3.289952  
  6. 快速冪演算法結果5  
  7. 快速冪演算法耗時:0.006706  
我們現在知道了快速冪取模演算法的強大了,我們現在來看核心原理:
  1. 對於任何一個整數的模冪運算  
  2. a^b%c  
  3. 對於b我們可以拆成二進位制的形式  
  4. b=b0+b1*2+b2*2^2+...+bn*2^n  
  5. 這裡我們的b0對應的是b二進位制的第一位  
  6. 那麼我們的a^b運算就可以拆解成  
  7. a^b0*a^b1*2*...*a^(bn*2^n)  
  8. 對於b來說,二進位制位不是0就是1,那麼對於bx為0的項我們的計算結果是1就不用考慮了,我們真正想要的其實是b的非0二進位制位  
  9. 那麼假設除去了b的0的二進位制位之後我們得到的式子是  
  10. a^(bx*2^x)*...*a(bn*2^n)  
  11. 這裡我們再應用我們一開始提到的公式,那麼我們的a^b%c運算就可以轉化為  
  12. (a^(bx*2^x)%c)*...*(a^(bn*2^n)%c)  
  13. 這樣的話,我們就很接近快速冪的本質了  
  1. (a^(bx*2^x)%c)*...*(a^(bn*2^n)%c)  
  2. 我們會發現令  
  3. A1=(a^(bx*2^x)%c)  
  4. ...  
  5. An=(a^(bn*2^n)%c)  
  6. 這樣的話,An始終是A(n-1)的平方倍(當然加進去了取模勻速那),依次遞推  
現在,我們基本的內容都已經瞭解到了,現在我們來考慮實現它:
  1. int quick(int a,int b,int c)  
  2. {  
  3.     int ans=1;   //記錄結果
  4.     a=a%c;   //預處理,使得a處於c的資料範圍之下
  5.     while(b!=0)  
  6.     {  
  7.         if(b&1) ans=(ans*a)%c;   //如果b的二進位制位不是0,那麼我們的結果是要參與運算的
  8.         b>>=1;    //二進位制的移位操作,相當於每次除以2,用二進位制看,就是我們不斷的遍歷b的二進位制位
  9.         a=(a*a)%c;   //不斷的加倍
  10.     }  
  11.     return ans;  
  12. }  

現在,我們的快速冪已經講完了我們來大致的推演一下快速冪取模演算法的時間複雜度首先,我們會觀察到,我們每次都是將b的規模縮小了2倍那麼很顯然,原本的樸素的時間複雜度是O(n)快速冪的時間複雜度就是O(logn)無限接近常熟的時間複雜度無疑逼樸素的時間複雜度優秀很多,在資料量越大的時候,者中優化效果越明顯

3.OJ例題

POJ1995題意:快速冪版題
  1. #include"iostream"
  2. #include"cstdio"
  3. #include"cstring"
  4. #include"cstdlib"
  5. usingnamespace std;  
  6. int ans=0;  
  7. int a,b;  
  8. int c;  
  9. int quick(int a,int b,int c)  
  10. {  
  11.     int ans=1;  
  12.     a=a%c;  
  13.     while(b!=0)  
  14.     {  
  15.         if(b&1) ans=(ans*a)%c;  
  16.         b>>=1;  
  17.         a=(a*a)%c;  
  18.     }  
  19.     return ans;  
  20. }  
  21. int main()  
  22. {  
  23.     int for_;  
  24.     int t;  
  25.     scanf("%d",&t);  
  26.     while(t--)  
  27.     {  
  28.         ans=0;  
  29.         scanf("%d%d",&c,&for_);  
  30.         for(int i=1;i<=for_;i++)  
  31.         {  
  32.             scanf("%d%d",&a,&b);  
  33.             ans=(ans+quick(a,b,c))%c;  
  34.         }  
  35.         printf("%d\n",ans);  
  36.     }  
  37.     return 0;  
  38. }