大數階乘
阿新 • • 發佈:2020-08-18
求階乘第一版
由於陣列長度採用遞迴,資料太大記憶體不夠。
package com.example.common.factorial; /** * 求階乘. * 基於jvm執行記憶體有限。大概12000以上的數字由於遞迴佔用會有溢位錯誤。 * 且本方法不考慮非法情況,預設使用者正確輸入正整數。 * main()方法提供呼叫示例,並列印執行時間,執行結果與結果的位數。 **/ public class Factorial0 { /** * 求{@code num}的階乘 * * @param num 要求階乘的數字 * @return 結算結果組成的字串*/ public static String factorial(int num) { int[] data = new int[(int) StrictMath.ceil(capacity(num))]; //目前陣列有效數字的長度 int size = 0; //進位 int carryBit = 0; //如果階乘數是1,直接插入 data[size++] = 1; //1已經插入,從2開始運算,直到相乘到階乘數 for (int factor = 2; factor <= num; factor++) {//計算的結果倒序儲存在陣列中,每次從陣列的第一位就是上次結果的末尾開始遍歷與factor做乘法。 //結果對進位制(預設10)取餘就是當前陣列位數對應的結果,對進製取商就是進位的數。 //在陣列的下一位與factor做乘法時加上上次的進位在對進製取餘,就是陣列中下一位的對應的結果,對進製取商進位。 for (int i = 0; i < size; i++) { int box = data[i] * factor + carryBit; data[i] = box % 10; carryBit= box / 10; } //對最後一個factor結果取餘就是當前陣列的最後一位的值,取商如果不為0,size增加,陣列長度變長。 while (carryBit != 0) { data[size++] = carryBit % 10; carryBit /= 10; } } //數字的有效位數為size,倒序組成String列印就是階乘的結果. StringBuilder sb = new StringBuilder(size); for (int i = size - 1; i >= 0; i--) { sb.append(data[i]); } return sb.toString(); } /** * 獲取階乘數的長度。 * 原理:數字 x 的長度=以10為底x的對數 * n*(n-1)*...*2*1的長度=log(n*(n-1)*...*2*1)=log(n)+log(n-1)+...+log(2)+log(1) * 方法用於建立陣列,所以加長了一位。 * * @param num 階乘 * @return 階乘的實際長度+1 */ private static double capacity(int num) { return num == 1 ? 1 : StrictMath.log10(num) + capacity(num - 1); } public static void main(String[] args) { long l = System.currentTimeMillis(); String factorial = factorial(7665); System.out.println(System.currentTimeMillis() - l); System.out.println(factorial); System.out.println(factorial.length()); } }
求階乘優化版。
思想是通過採用較大的進位制,減少陣列的長度,降低二層迴圈中迴圈的次數,加快執行速度。
陣列長度遞迴換for迴圈,遞迴太消耗記憶體,支援更大的數(也只是基本型別表達的數)。
進位制10進位制換成動態進位制。陣列儲存10進位制的數浪費空間,進位制要滿足陣列中的任意數字與最大階乘數相乘結果在陣列的類型範圍。
package com.example.common.factorial; /** * 求階乘. * main()方法提供呼叫示例,並列印執行時間,執行結果與結果的位數。 **/ public class Factorial2 { private static final String ZERO = "0"; /** * 求{@code num}的階乘 * * @param num 要求階乘的數字 * @return 結算結果組成的字串 */ public static String factorial(int num) { long scale = getScale(num); int size = 0; long carryBit = 0L; double capacity = 0D; for (int i = 1; i <= num; i++) { capacity += StrictMath.log10(i); } //因為採用大進位制,陣列的長度=log以scale為底n*(n-1)...*2*1 // 換底公式=log(n*(n-1)*...*2*1)/log(scale) capacity = capacity / StrictMath.log10(scale); long[] data = new long[(int) StrictMath.ceil(capacity) + 1]; data[size++] = 1; for (int factor = 2; factor <= num; factor++) { for (int i = 0; i < size; i++) { long box = data[i] * factor + carryBit; data[i] = box % scale; carryBit = box / scale; } while (carryBit != 0) { data[size++] = carryBit % scale; carryBit /= scale; } } StringBuilder sb = new StringBuilder(); for (int i = size - 1; i >= 0; i--) { for (int i1 = 0; i1 < leftZeroCount(data[i], scale); i1++) { sb.append(ZERO); } sb.append(data[i]); } return sb.toString(); } /** * 獲取最大的十進位制的冪的進位制。最小為10進位制。 * 因為最終表示為10進位制的字串,採用10的冪作為進位制,可以直接按位組合。 * 參考 * 二進位制 111111111111 * 八進位制 8888 * 十六進位制FFF * * @param num 階乘數 * @return 進位制 */ private static long getScale(int num) { long pow = (long) StrictMath.pow(10, StrictMath.floor(StrictMath.log10(Long.MAX_VALUE) - StrictMath.log10(num))); return pow >= 10 ? pow : 10; } /** * 舉例: * 10000進位制(3999)(0)(9) * 10進位制 9*(10000)+0*(10000e1)+3999*(10000e2) * =3999 0000 0009 * 因為進製為10的冪次方,轉為10進位制的時候,只要在對應的位左補零拼接在一起就是10進位制。 * * @param num 階乘數 * @param scale 進位制 * @return 左邊需要補零的個數 */ private static int leftZeroCount(long num, long scale) { //如果num=0,因為自己也佔一位少補一個0. return num == 0 ? (int) StrictMath.log10(scale) - 1 : (int) (StrictMath.log10(scale) - StrictMath.ceil(StrictMath.log10(num))); } public static void main(String[] args) { int time = 1; long l = System.currentTimeMillis(); String factorial = null; for (int i = 0; i < time; i++) { factorial = factorial(7665); } System.out.println(time + "次平均執行時間" + (System.currentTimeMillis() - l) / time + "ms"); System.out.println(factorial); } }