计算阶乘的对数

时间:2019-05-27 01:23:56

标签: performance

这里有三种计算阶乘N的对数的方法:lgN! 第一种方法使用递归并将每个递归值存储在数组中 第二种方法使用不带数组的递归 第三种方法使用FOR循环。

经过测试,第三种方法的性能最好,第二种方法的第一种方法的性能很差。

我的问题是为什么第一种方法比第二种方法差 尽管第三种方法具有最佳性能,但它的价值与其他方法不同,但是有什么错误吗?

/**
 * method 1 
 * calculate lgN! with recursion and keep every recursive value
     * in an array. It has bad performance, once n reached about 6000, it 
     * will throw stackoverflow exception
 * @param a
 * @param n
 * @return
 */
private static double lgNFact1(double[] a ,int n){
    if(n==1)
        a[n-1]=0;
    else
        a[n-1]=Math.log(n) + lgNFact1(a,n-1);
    return a[n-1];
}

/**
 * Method 2 have a little better performance, in my case n can reach 10000!,than throws stackoverflow exception
 * @param n
 * @return
 */
private static double lgNFact2(int n) {
    if(n==1)
        return 0;
    return Math.log(n)+lgNFact2(n-1);
}
/**
     * Method 3, based on logarithm formula, we can use for loop to get lgN!
 * log5!=log5+log4+log3+log2+log1

 * @param n
 * @return
 */
private static double lgNFact3(int n) {
    double sum = 0;
    for(int i=1;i<=n;i++) {
        sum+=Math.log(n);
    }
    return sum;
}

下面是测试,当n等于10时,我们可以看到第三个与上面两种情况不同

case1 n = 10,结果:15.104412573075518

case2 n = 10,结果:15.104412573075518

case3 n = 10,结果:23.025850929940464

3 个答案:

答案 0 :(得分:0)

使用递归解决方案时,对函数的每次调用都会为该函数设置内存位置,并在执行计算之前连接有关调用者的元数据等。 for循环仅计算值并完成。第一种方法除了要处理数字以外,还必须处理复制数组,因此需要花费更长的时间。

答案 1 :(得分:0)

总结:方法1正在堆栈上复制一个数组,这需要时间和内存,因此其上限为6000。方法2是递归的,因此也可以限制堆栈的增长量。它必须花费时间为每个递归调用分配内存。方法3不使用递归调用,因此不必膨胀堆栈,也不必花费时间推送堆栈帧。

答案 2 :(得分:0)

哪种方法最有效或方法2的尾调用变式取决于语言。就目前而言,方法2不使用尾调用递归。一个简单的更改以使其使用尾调用:

/**
 * Tail call method 2 may or may not throw a stackoverflow exception, depending
 * on whether the language supports tail call optimization.
 */
private static double tailLgNFact2 (int n, double sum=0.0)
{
    if (n <= 1) return sum;
    return tailLgNFact2(n-1, Math.log(n)+sum);
}

基于堆栈溢出时的其他问题,Java编译器显然不支持尾部调用优化,但是某些Java运行时显然支持。使用确实支持尾部调用优化的功能编程语言,尾部调用版本可能比循环版本具有更好的性能。