打工人必會算法—快速冪算法講解

{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":1}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"前言"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"快速冪是什麼?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"顧名思義"},{"type":"text","text":",快速冪就是快速算底數的n次冪。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"有多快?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"其時間複雜度爲 O(log₂n), 與樸素的O(n)相比效率有了極大的提高。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"用的多麼?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"快速冪屬於數論的範疇,本是ACM經典算法,但現在各廠對算法的要求越來越高,並且快速冪適用場景也比較低多並且相比樸素方法有了非常大的提高。所以掌握快速冪算法已經是一名更合格的工程師必備要求!"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下面來詳細看看快速冪算法吧!"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"快速冪介紹"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"先看個問題再說:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"初探"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先問你一個問題,如果讓你求 "},{"type":"text","marks":[{"type":"strong"}],"text":"(2^10)%1000"},{"type":"text","text":"你可能會這樣寫:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"int va=1;\nfor(int i=0;i<10;i++)\n{\n va*=2;\n}\nSystem.out.println(va%10000);"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"熟悉的1024沒問題,"},{"type":"text","marks":[{"type":"strong"}],"text":"總共計算了10次"},{"type":"text","text":"。但是如果讓你算 "},{"type":"text","marks":[{"type":"strong"}],"text":"(2^50)%10000"},{"type":"text","text":"呢?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"你可能會竊喜,小樣,這就想難住我?我知道int只有32位,50位超出範圍會帶來數值越界的異常,我這次可以用long,long有64位呢!"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"long va=1;\nfor(int i=0;i<50;i++)\n{\n va*=2;\n}\nSystem.out.println(va);\nSystem.out.println(va%10000);"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"計算50次出了結果正當你暗暗私喜的時候又來了一個要命的問題:讓你算 "},{"type":"text","marks":[{"type":"strong"}],"text":"(2^1e10)%10000"},{"type":"text","text":" 且不許你用Java大數類,你爲此苦惱不知所措。這時bigsai小哥哥讓你百度下取模運算,然後你恍然大悟,在紙上寫了幾個公式:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"(a + b) % p = (a % p + b % p) % p (1)\n(a - b) % p = (a % p - b % p ) % p (2)\n(a * b) % p = (a % p * b % p) % p (3)\na ^ b % p = ((a % p)^b) % p (4)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"你還算聰明一眼發現其中的規律:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"(a * b) % p = (a % p * b % p) % p (3)\n(2*2*2···*2) %1e10=[2*(2*2···*2)]%1e5=(2%1e5)*(2*2···*2%le5)%1e5"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"憑藉這個遞推你明白:每次相乘都取模。機智的你pia pia寫下以下代碼,卻發現另一個問題:怎麼跑不出來?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/e0/e0c8880e936fb0cccbede1948ce5a1b9.png","alt":"image-20201028160221192","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"咱們打工人需要對計算機運行速度和數值有一個大致的概念。循環體中不同操作佔用時間不同,"},{"type":"text","marks":[{"type":"strong"}],"text":"所以當你的程序循環次數到達1e6或1e7的時候就需要非常非常小心了"},{"type":"text","text":"。如果循環體邏輯或者運算較多可能非常非常慢。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/c3/c3c373952c6d2415d0c51224e34ab121.png","alt":"image-20201028163737620","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"快速冪探索"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"機智的你不甘失敗,開始研究其數的規律,將這個公式寫在手上、膀子上、小紙條上。喫飯睡覺都在看:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/8e/8e1d8b7258b3df6ed5f255c40476d637.png","alt":"image-20201028171029641","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"然後你突然發現其中的奧祕,n次冪可以拆分成一個平方計算後就剩餘n/2的次冪了:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/bd/bddecbc28df443df5e7bbf85e8303e40.png","alt":"image-20201028174250098","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"現在你已經明白了快速冪是怎麼回事,但你可能有點上頭,還是給我講了很多內容:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/7a/7afd4ae38bd6f224f30efa750e29938b.png","alt":"image-20201028180224832","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"快速冪實現"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"至於快速冪已經懂了,我們該怎麼實現這個算法呢?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/39/3964c7d7c2617fbe69ebc5f219ea326c.png","alt":"image-20201028185101226","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"說的不錯,確實有遞歸和非遞歸的實現方式,但是遞歸使用的更多一些。在實現的時候,注意一下奇偶性、停止條件就可以了,奇數問題可以轉換爲偶數問題:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"2*2*2*2*2=2 * (2*2*2*2) 奇數問題可以轉化爲偶數問題。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這裏,"},{"type":"text","marks":[{"type":"strong"}],"text":"遞歸的解法如下"},{"type":"text","text":":"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"long c=10000007;\npublic long divide(long a, long b) {\n\t\tif (b == 0)\n\t\t\treturn 1;\n\t\telse if (b % 2 == 0) //偶數情況\n\t\t\treturn divide((a % c) * (a % c), b / 2) % c;\n else//奇數情況\n\t\t\treturn a % c * divide((a % c) * (a % c), (b - 1) / 2) % c;\n\t}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"非遞歸實現也不難,控制好循環條件即可:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"//求 a^b%1000000007\nlong c = 1000000007;\npublic long divide(long a, long b) {\n a %= c;\n long res = 1;\n for (; b != 0; b /= 2) {\n if (b % 2 == 1)\n res = (res * a) % c;\n a = (a * a) % c;\n }\n return res;\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對於非遞歸你可能有點模糊"},{"type":"text","marks":[{"type":"strong"}],"text":"爲啥偶數情況不給res賦值"},{"type":"text","text":"。這裏有兩點:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲奇數的情況"},{"type":"text","marks":[{"type":"strong"}],"text":"res僅僅是收集相乘那個時候落單的a"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最終b均會降到1,a最終都會和res相乘,不用擔心會漏掉"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"理想狀態一直是偶數情況,那最後直接獲得a取模的值即可。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果還是不懂,可以用這個圖來解釋一下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/73/73103e24d2d172b7952eed38ffc4373f.png","alt":"image-20201028192842778","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"矩陣快速冪"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"你以爲這就結束了?雖然快速冪主要內容就是以上內容,但是總有很多牛人能夠發現很有趣的規律—"},{"type":"text","marks":[{"type":"strong"}],"text":"矩陣快速冪"},{"type":"text","text":"。如果你沒聽過的話建議仔細看看了解一下。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"大家都知道斐波那契數列: 的規則:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" "}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/54/54489e064e34f50f09e357bb6d9bcf14.png","alt":"image-20201028193231170","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"前幾個斐波那契的數列爲:0, 1, 1, 2, 3, 5, 8, 13, 21, 34, …"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"斐波那契從遞推式就可以看出"},{"type":"text","marks":[{"type":"strong"}],"text":"是指數級別的增長"},{"type":"text","text":",所以稍微多幾個數字就是爆炸式增長,所以很多時候也會要求最後幾位的結果。有了前面模運算公式溢出就不成問題,但n如果非常非常大怎麼快速計算就成了一個新的問題。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們看下面一組公式:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"f(n+1) = f(n) + f(n-1)\nf(n) = f(n)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果那f(n)和f(n-1)放到一個矩陣中(一行兩列):"},{"type":"codeinline","content":[{"type":"text","text":"[f(n+1),f(n)]"}]},{"type":"text","text":" 能否找到和"},{"type":"codeinline","content":[{"type":"text","text":"[f(n),f(n-1)]"}]},{"type":"text","text":"之間的什麼規律呢?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"答案是存在規律的,看上面的公式知道"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"[f(n+1),f(n)]\n=[f(n)+f(n-1),f(n)]\n\n [1 1]\n=[f(n),f(n-1)] * \n [1 0]\n \n [1 1] [1 1]\n=[f(n-1),f(n-2)]* *\n [1 0] [1 1] \n\n=······· "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以現在你可以知道它的規律了吧,這樣一直迭代到f(2),f(1)剛好都爲1,所以這個斐波那契的計算爲:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/04/04d6208ce925212928b9692d73140c7e.png","alt":"image-20201028195631635","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"而這個矩陣有很多次冪,就可以使用快速冪啦,原理一致,你只需要寫一個矩陣乘法就可以啦,下面提供一個矩陣快速冪求斐波那契第n項的後三位數的模板,可以拿這個去試一試poj3070的題目啦。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public int Fibonacci(int n)\n {\n n--;//矩陣爲兩項\n int a[][]= {{1,1},{1,0}};//進行快速冪的矩陣\n int b[][]={{1,0},{0,1}};//存儲漏單奇數、結果的矩陣,初始爲單位矩陣\n int time=0;\n while(n>0)\n {\n if(n%2==1)\n {\n b=matrixMultiplication(a, b);\n }\n a=matrixMultiplication(a, a);\n n/=2;\n }\n return b[0][0];\n }\n public int [][]matrixMultiplication(int a[][],int b[][]){//\n int x=a.length;//a[0].length=b.length 爲滿足條件\n int y=b[0].length;//確定每一排有幾個\n int c[][]=new int [x][y];\n for(int i=0;i
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章