java递归基础掉用
希望您终于弄清楚了递归是指其自身的任何内容(如果不是,那么您可能会永远浏览Google并试图找出递归是什么!)。 递归的一个相当常见的例子是斐波那契数。 斐波那契数的模式是将前两个项相加,下一个项以一个和一个开始。
以下是斐波那契数的递归关系:
F(1)= F(2)= 1
F(n)= F(n-1)+ F(n-2)
递归关系是原始函数引用自身的任何关系。 那么我们如何找到F(5)?
F(5)= F(4)+ F(3)
F(5)= [F(3)+ F(2)] + [F(2)+ F(1)] F(5)= [F(2)+ F(1)] +1 +1 +1
F(5)= 1 + 1 + 1 + 1 + 1 F(5)= 5
似乎需要很多工作? 好吧,对于计算机来说,大多数时候都相当快。 稍后,我将向您介绍动态编程,因此当您要计算较大的斐波那契数时,我们可以加快速度。
那么递归函数的所有部分是什么? 首先,什么是递归函数? 递归函数是任何直接(间接或间接)调用自身的函数。 这是Java中的一个简单示例:
public static void doIt()
{doIt();
}
当然,这最终会导致堆栈溢出错误,因此不建议您实际尝试使用此代码。
所有有用的递归函数都具有以下一般属性:减少问题,直到计算机可以轻松解决为止。 为此,递归函数必须具有:
- 定义了基本案例(解决方案显而易见且无法进一步简化的案例)
- 减少步骤(简化给定问题的地方)
- 递归调用自身(基本上解决了更简单的情况)
在上面的Fibonacci递归函数中,您可以看到它一直在递归直到只加1。 这是因为在斐波那契数列中1是基本情况,因此我们仅需将1加几次才能得到F(n)。
从理论上讲,所有递归函数都可以迭代编写,并且所有迭代函数都可以递归编写。 但是,在实践中,您会发现根据情况的不同,其中一种或两种理念会更好地发挥作用。
让我们递归地查看阶乘函数,并将其与它的迭代亲戚进行比较。
阶乘(N)= 1 * 2 * 3 * ... * N
基本上,将1到N的所有整数相乘即可得到N的阶乘。
迭代实现,您的代码将如下所示:
public static int iterativeFactorial(int n)
{int answer = 1;for (int i = 1; i < n; i++){answer *= i;}return answer;
}
我们还可以编写此函数的递归等效项:F(1)= 1 F(N)= F(N-1)* N您能看到这如何产生与迭代阶乘相同的结果吗? 这是递归计算阶乘的代码:
public static int recursiveFactorial(int n)
{if (n == 1){return 1;}else{return n * recursiveFactorial(n-1);}
}
那么,就性能而言,递归如何与此处的迭代解决方案相提并论? 可悲的是,答案很差。 此处的递归函数需要大量内存来存储方法堆栈并跟踪每个递归调用中的所有变量,而迭代解决方案仅需跟踪当前状态。 那么,为什么还要烦恼递归呢? 因为很多时候正确使用递归,它的性能可能会超过迭代解决方案的性能,并且递归函数也可能更容易编写(有时)。
动态编程
动态编程是递归的一种形式,但是它是迭代实现的。 还记得我们上面的斐波那契计算机吗? F(5)= F(2)+ F(1)+ F(2)+ F(2)+ F(1)F(5)= 3 * F(2)+ 2 * F(1)我们有这里有一些“过度计算”。 只需计算一次F(2)和一次F(1)。 在这种情况下,计算这几个项并不需要太多的计算任务,但是在某些情况下,几乎不可能数百次重新计算解决方案。 因此,我们无需重新计算,而是将答案存储了下来。
public static int dynamicFibonacci(int n)
{int[] prevSolutions = new int[n];if (n == 1 || n == 2){return 1;}prevSolutions[0] = 1;prevSolutions[1] = 1;for (int i = 2; i < prevSolutions.length; i++){prevSolutions[i] = prevSolutions[i-1] + prevSolutions[i-2];}return prevSolutions[n-1];
}
因此,再次取F(5)。 如果我们以递归方式进行操作,那么将有8次调用recursiveFibonacci。 但是,这里我们只计算一次F(1),F(2),F(3),F(4)和F(5)。 减少3个电话获得的收益似乎并不多,但是如果我们尝试计算F(50)怎么办? dynamicFibonacci仅会计算50个数字,但递归Fibonacci可能会超过1000个(当然,我还没有计算,所以我不知道它是否超过1000个)。
关于动态编程的最后一点是,它仅在有大量重叠的情况下才有用。 还记得recursiveFactorial函数吗? 如果我们调用recursiveFactorial(50)和dynamicFactorial(50),则它们将花费大致相同的时间,因为我们要进行相同数量的计算。 这是因为从来没有重叠。 这也是为什么排序算法不是通过动态编程实现的较差选择的原因:如果您分析大多数排序算法,则它们几乎没有重叠的解决方案,因此对于动态编程来说是一个较差的选择。
这是有关递归和动态编程的一些问题:
- 实现recursiveFactorial方法(您以为我忘了把它放在那里)
- 对于给定的递归关系,编写一个递归方法,将找到F(N)
- 这种递归关系在迭代方面意味着什么? 为此问题写一个迭代的解决方案。
- 这种递归关系是否适合动态编程? 为什么/为什么不呢?
- 有比迭代或递归解决方案更好的方法来解决此问题吗? 这是什么(如果有)? 提示:想想卡尔高斯
对于问题2-5,请使用以下递归关系:
F(1)= 1
F(N)= F(N-1)+ N
答案来了……
参考: Java编程论坛上 JCG合作伙伴的 递归
相关文章 :
- Java Micro-Benchmarking:如何编写正确的基准
- 首先记录异常的根本原因
- 编程反模式
- 您不想错过的十大Java书籍
- Java日志混乱
- 做短,但做对!
翻译自: https://www.javacodegeeks.com/2011/12/java-recursion-basics.html
java递归基础掉用