算法—史上最好快速冪算法講解

將bigsai設爲星標,方便下次觀看哦!python

前言

快速冪是什麼?web

  • 顧名思義,快速冪就是快速算底數的n次冪。算法

有多快?微信

  • 其時間複雜度爲 O(log₂n), 與樸素的O(n)相比效率有了極大的提升。app

用的多麼?ide

  • 快速冪屬於數論的範疇,本是ACM經典算法,但如今各廠對算法的要求愈來愈高,而且快速冪適用場景也比較多而且相比樸素方法有了很是大的提升。因此掌握快速冪算法已是一名更合格的工程師必備要求!優化

下面來詳細看看快速冪算法吧!ui

快速冪介紹

先看個問題再說:spa

初探

首先問你一個問題,若是讓你求 (2^10)%1000你可能會這樣寫:.net

int va=1;
for(int i=0;i<10;i++)
{
  va*=2;
}
System.out.println(va%10000);

熟悉的1024沒問題,總共計算了10次。可是若是讓你算 (2^50)%10000呢?

你可能會竊喜,小樣,這就想難住我?我知道int只有32位,50位超出範圍會帶來數值越界的異常,我此次能夠用long,long有64位呢!

long va=1;
for(int i=0;i<50;i++)
{
  va*=2;
}
System.out.println(va);
System.out.println(va%10000);

計算50次出告終果正當你暗暗私喜的時候又來了一個要命的問題:讓你算 (2^1e10)%10000 且不准你用Java大數類,你爲此苦惱不知所措。這時bigsai小哥哥讓你百度下取模運算,而後你恍然大悟,在紙上寫了幾個公式:

(a + b) % p = (a % p + b % p) % p  (1
(a - b) % p = (a % p - b % p ) % p (2
(a * b) % p = (a % p * b % p) % p  (3
a ^ b % p = ((a % p)^b) % p        (4

你還算聰明一眼發現其中的規律:

(a * b) % p = (a % p * b % p) % p   (3)
(2*2*2···*2) %1e10=[2*(2*2···*2)]%1e5=(2%1e5)*(2*2···*2%le5)%1e5

憑藉這個遞推你明白:每次相乘都取模。機智的你pia pia寫下如下代碼,卻發現另外一個問題:怎麼跑不出來?

我們打工人須要對計算機運行速度和數值有一個大體的概念。循環體中不一樣操做佔用時間不一樣,因此當你的程序循環次數到達1e6或1e7的時候就須要很是很是當心了。若是循環體邏輯或者運算較多可能很是很是慢。

快速冪探索

機智的你不甘失敗,開始研究其數的規律,將這個公式寫在手上、膀子上、小紙條上。吃飯睡覺都在看:

而後你忽然發現其中的奧祕,n次冪能夠拆分紅一個平方計算後就剩餘n/2的次冪了:

如今你已經明白了快速冪是怎麼回事,但你可能有點上頭,仍是給我講了不少內容:

快速冪實現

至於快速冪已經懂了,咱們該怎麼實現這個算法呢?

說的不錯,確實有遞歸和非遞歸的實現方式,可是遞歸使用的更多一些。在實現的時候,注意一下奇偶性、中止條件就能夠了,奇數問題能夠轉換爲偶數問題:

2*2*2*2*2=2 * (2*2*2*2) 奇數問題能夠轉化爲偶數問題。

這裏,遞歸的解法以下

long c=10000007;
public  long divide(long a, long b) {
        if (b == 0)
            return 1;
        else if (b % 2 == 0//偶數狀況
            return divide((a % c) * (a % c), b / 2) % c;
    else//奇數狀況
            return a % c * divide((a % c) * (a % c), (b - 1) / 2) % c;
    }

非遞歸實現也不難,控制好循環條件便可:

//求 a^b%1000000007
long c = 1000000007;
public  long divide(long a, long b) {
  a %= c;
  long res = 1;
  for (; b != 0; b /= 2) {
    if (b % 2 == 1)
      res = (res * a) % c;
    a = (a * a) % c;
  }
  return res;
}

對於非遞歸你可能有點模糊爲啥偶數狀況不給res賦值。這裏有兩點:

  • 爲奇數的狀況res僅僅是收集相乘那個時候落單的a

  • 最終b均會降到1,a最終都會和res相乘,不用擔憂會漏掉

  • 理想狀態一直是偶數狀況,那最後直接得到a取模的值便可。

若是仍是不懂,能夠用這個圖來解釋一下:


矩陣快速冪

你覺得這就結束了?雖然快速冪主要內容就是以上內容,可是總有不少牛人可以發現頗有趣的規律—矩陣快速冪。若是你沒聽過的話建議仔細看看了解一下。

你們都知道斐波那契數列:的規則:

前幾個斐波那契的數列爲:0, 1, 1, 2, 3, 5, 8, 13, 21, 34, …

斐波那契從遞推式就能夠看出是指數級別的增加,因此稍微多幾個數字就是爆炸式增加,因此不少時候也會要求最後幾位的結果。有了前面模運算公式溢出就不成問題,但n若是很是很是大怎麼快速計算就成了一個新的問題。

咱們看下面一組公式:

f(n+1) = f(n)   + f(n-1)
f(n)   = f(n)

若是f(n)和f(n-1)放到一個矩陣中(一行兩列):[f(n+1),f(n)] 可否找到和[f(n),f(n-1)]之間的什麼規律呢?

答案是存在規律的,看上面的公式知道

[f(n+1),f(n)]
=[f(n)+f(n-1),f(n)]

                 [1  1]
=[f(n),f(n-1)]  *      
                 [1  0]

                 [1  1] [1   1]
=[f(n-1),f(n-2)]*      *
                 [1  0] [1   1]  

=·······           

因此如今你能夠知道它的規律了吧,這樣一直迭代到f(2),f(1)恰好都爲1,因此這個斐波那契的計算爲:

而這個矩陣有不少次冪,就可使用快速冪啦,原理一致,你只須要寫一個矩陣乘法就能夠啦,下面提供一個矩陣快速冪求斐波那契第n項的後三位數的模板,能夠拿這個去試一試poj3070的題目啦。

public int Fibonacci(int n)
    
{
        n--;//矩陣爲兩項
        int a[][]= {{1,1},{1,0}};//進行快速冪的矩陣
        int b[][]={{1,0},{0,1}};//存儲漏單奇數、結果的矩陣,初始爲單位矩陣
        int time=0;
        while(n>0)
        {
            if(n%2==1)
            {
                b=matrixMultiplication(a, b);
            }
            a=matrixMultiplication(a, a);
            n/=2;
        }
        return b[0][0];
    }
 public  int [][]matrixMultiplication(int a[][],int b[][]){//
        int x=a.length;//a[0].length=b.length 爲知足條件
        int y=b[0].length;//肯定每一排有幾個
        int c[][]=new int [x][y];
        for(int i=0;i<x;i++)
            for(int j=0;j<y;j++)
            {
                //須要肯定每個元素
                //c[i][j];
                for(int t=0;t<b.length;t++)
                {
                    c[i][j]+=(a[i][t]%10000)*(b[t][j]%10000);
                    c[i][j]%=10000;
                }
            }
        return c;
    }

結語

這篇到這裏就肝完啦,其實快速冪的內容還不止這麼多,尤爲是矩陣快速冪,會有着各類巧妙的變形,不過跟數學有一些關係,這年頭,不會點算法、不會點數學真的是舉步維艱。因此你們要對本篇內容好好吸取,讓我那麼久的努力發揮出做用。

若是有疑問不懂得歡迎私聊我討論。也但願你們點個在看,您的支持是我努力的不斷動力。

近期精彩:
用python實現一個豆瓣通用爬蟲(登錄、爬取、可視化分析)
硬核!手寫一個優先隊列
回溯算法 | 追憶那些年曾難倒咱們的八皇后問題
刷題一個4ms的程序,代碼如何優化到3ms再到2ms?
MongoDB助力一個物流訂單系統

關注bigsai,回覆bigsai領取乾貨資源,回覆進羣加入力扣打卡羣。下次再見,打工人!





本文分享自微信公衆號 - bigsai(bigsai)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索