阶乘的另一种求法,尾-递法

369 阅读2分钟

1.阶乘的常见-迭代求法

 /**
  * 迭代求法
  *
  * @param n 阶乘数
  * @return
  */
 static long factorIterative(long n) {
     long r = 1;
     for (int i = 1; i <= n; i++) {
         r *= i;
     }
     return r;
 }

 static long factorRecursive(long n) {
     return n == 1 ? 1 : n * factorIterative(n - 1);
 }

2.递归调用-尾巴递归(尾-递法)

/**
 * 递归调用-尾巴递归(尾-递法)
 *
 * @author houxiurong
 * @date 2019-10-02
 */
public class TailRecursiveMethod {
    /**
     * 递归调用-尾巴递归(尾-递法)
     *
     * @param n 阶乘位数
     * @return 1*2*3...*n
     */
    static long factorTailRecursive(long n) {
        return factorHelper(1, n);
    }

    private static long factorHelper(long acc, long n) {
        return n == 1 ? acc : factorHelper(acc * n, n - 1);
    }

    public static void main(String[] args) {
        System.out.println(factorTailRecursive(5));
    }
}

总结

根据上面两个不同的处理方式,来总结下程序执行的效率问题。作为Java的用户,我们应该强烈建议使用递归,摈弃迭代。一般来说,执行一次递归式方法调用的开销要比迭代执行单一机器的分支治疗大很多。每次执行factorRecursive方法调用都会在调用栈上创建一个新的栈帧(局部变量表,操作栈,动态链接,返回地址),用户保存每个方法调用的状态(需要进行的乘法运算),这个操作会一直指导程序运行直到结束。这个就意味着递归方法会根据他接收的输入成比例地消耗内存。最后如果使用一个大型输入执行factorRecursive方法,容易导致StackOverflowError

Execption in thread "main" java.lang.StackOverflowError

上面的执行对于小规模的数值递归是没有问题的。使用函数式语言提供了更好的实现方式:尾-递法。这种新型的迭代调用优化后执行速度要超级快。递归调用发生在方法的最后,不需要在不同的栈帧上保存每次递归计算的中间值,编译器能够很好的优化决定复用这个栈帧进行计算,为编译器的优化(逃逸分析)很有帮助。