在JAVA中求階乘首先遇到的問題就是結果溢出,不管是使用int還是long,double都無法表示1000!這么大的天文數字,這里暫且用BigInteger解決這個問題!
下面是使用遞歸和尾遞歸分別計算1000的階乘:
1 import java.math.BigInteger; 2 3 public class Main { 4 5 public static void main(String[] args) { 6 long t = System.currentTimeMillis(); 7 System.out.println(factorial(new BigInteger("1000"))); 8 System.out.println(System.currentTimeMillis()- t); 9 t = System.currentTimeMillis(); 10 System.out.println(factorial2(new BigInteger("1000"),BigInteger.ONE)); 11 System.out.println(System.currentTimeMillis()- t); 12 } 13 14 15 /** 16 * 使用線性遞歸計算階乘 17 * @param n 18 * @return 19 */ 20 public static BigInteger factorial(BigInteger n ){ 21 if (n.compareTo(BigInteger.ZERO) < 0) return BigInteger.ZERO; 22 23 if (n.equals(BigInteger.ONE) || n.equals(BigInteger.ZERO)) { 24 return new BigInteger("1"); 25 } 26 return n.multiply(factorial(n.subtract(BigInteger.ONE))); 27 } 28 29 30 /** 31 * 使用尾遞歸計算階乘 32 * 如果一個函數中所有遞歸形式的調用都出現在函數的末尾,我們稱這個遞歸函數是尾遞歸的。 33 * 當遞歸調用是整個函數體中最后執行的語句且它的返回值不屬於表達式的一部分時,這個遞歸調用就是尾遞歸。 34 * 尾遞歸函數的特點是在回歸過程中不用做任何操作,這個特性很重要,因為大多數現代的編譯器會利用這種特點自動生成優化的代碼。 35 * 尾遞歸是極其重要的,不用尾遞歸,函數的堆棧耗用難以估量,需要保存很多中間函數的堆棧。 36 * 通過參數傳遞結果,達到不壓棧的目的 37 * @param n 38 * @param result 39 * @return 40 */ 41 public static BigInteger factorial2(BigInteger n,BigInteger result){ 42 if (n.compareTo(BigInteger.ZERO) < 0) return BigInteger.ZERO; 43 44 if (n.equals(BigInteger.ONE) || n.equals(BigInteger.ZERO)) { 45 return result; 46 } 47 48 return factorial2(n.subtract(BigInteger.ONE),n.multiply(result)); 49 } 50 51 }
輸出:
402387260077093773543702433923003985719374864210714632543799910...(太長了,省略)000 38
402387260077093773543702433923003985719374864210714632543799910...(省略)000
11
Process finished with exit code 0
從上面的代碼和運行結果可以看出,尾遞歸使得節省了中間函數堆棧的使用,使得性能大大提高(38-11=27毫秒)!