打工人必会算法—快速幂算法讲解

{"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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章