在查看以下MIT 6.001课程视频时,在28:00,教师将此算法标记为迭代。但是,在30.27他说这个和实际的“递归”算法都是递归的。 该函数使用基本情况调用自身,那么这个迭代是怎么回事?
https://www.youtube.com/watch?v=dlbMuv-jix8&list=PLE18841CABEA24090&index=2
private int iterativeSum(int x, int y)
{
System.out.println("x "+x+" y "+y);
if(x == 0)
{
return y;
}
return iterativeSum(--x, ++y);
}
答案 0 :(得分:4)
他似乎对它的执行方式更感兴趣,而不是如何编写代码。这两者之间存在很大差异,但这是另一个对话(但值得注意的是,某些语言会将递归编译为迭代,作为示例)。
在这种情况下,当您将整个状态保存在一个位置并重复处理该一个数据时,它是 iteration 。当你有一堆状态并添加到堆栈中时,它是递归,然后最终将堆栈折叠回到答案。
在他31:00的例子中,他表示这是一个迭代,当时有一点纸张保存到目前为止完成的工作的整个状态,任何一个人都可以接受并最终产生最终答案。
在32:20的递归示例中,Joe对问题有自己的注释,并且仅传递有关问题子部分的注释。然后哈利有足够的信息来解决这个问题,但整个问题仍然需要乔坚持自己的信息来处理哈利的结果,当他把他们从哈利带回来时。
你有一大堆人,有更多的人被添加到堆栈中,直到他们中的一个人有一个简单到他自己做的问题,他可以马上回复他的答案,这意味着现在的第二个人有一个更简单的问题,现在可以返回他的答案,依此类推,直到整个人群倒塌成最后一个(第一个)然后产生最终答案的人。
答案 1 :(得分:2)
我认为这是基于SICP中的定义。这是the relevant section.简而言之,如果递归调用处于尾部位置,递归函数可以生成迭代进程:没有局部变量'需要记住当前值,并且可以清除/重用它们的空间(使用LISP可以更容易地看到所有内容都是表达式,并且可以看到表达式的大小在迭代过程中不会增长)。
相反,递归过程在递归调用完成后尚未完成。例如,这个函数
private int recursiveSum(int x, int y)
{
if(x == 0)
{
return y;
}
return ++(recursiveSum(--x, y));
}
将生成递归过程,因为还需要完成额外的工作(++()
)。
编译器是否实际实现尾调用优化(TCO)是另一回事。 AFAIK,迄今为止JVM不支持它。在尾部位置调用自身的函数通常易于优化(作为循环)。当一个函数调用另一个函数,另一个函数调用第一个函数等时,就会遇到困难。
答案 2 :(得分:2)
在函数调用自身的意义上,它是递归的。但是,它具有一个重要的属性,即调用的结果仅依赖于 来自另一个函数调用的结果;不需要当前堆栈中的值。结果由
提供return iterativeSum(--x, ++y);
不是来自
之类的东西return iterativeSum(--x, ++y) + x;
这将需要从递归调用中“返回”,对结果执行某些操作,然后返回该结果。因为结果不需要来自当前堆栈帧的任何内容,所以实现(在某些语言中,取决于语义)可以消除或重用当前堆栈帧。这就是所谓的尾部调用消除,它在某些语言中是强制性的,比如Scheme。这就是为什么该算法的Scheme实现基本上是迭代的:它不需要无限量的堆栈空间。
在Scheme中,尾调用消除意味着实现基本上如下,其中 iterativeSumDriver 是各种类型的蹦床,或者是由 iterativeSumInternal提供的结果的迭代驱动程序强>
public class IterativeSummer {
/**
* Returns a sum, computed iteratively.
*
* @param x the augend
* @param y the addend
* @return the sum of the augend and addend
*/
public int iterativeSumDriver(int x, int y) {
int[] state = new int[] { x, y };
while (state.length == 2) {
state = iterativeSumInternal(state[0], state[1]);
}
return state[0];
}
/**
* Returns the new computation state of a iterative sum
* computation. If x is 0, then returns an array of just y.
* Otherwise, returns an an array of x-1 and y+1.
*
* @param x the augend
* @param y the addend
* @return the next interal state
*/
int[] iterativeSumInternal(int x, int y) {
if (x == 0) {
return new int[] { y };
}
else {
return new int[] { x-1, y+1 };
}
}
public static void main(String[] args) {
int x = 5;
int y = 6;
int sum = new IterativeSummer().iterativeSumDriver(x,y);
System.out.println(String.format("%d + %d = %d", x, y, sum));
}
}
作为Will Ness pointed out,正确的蹦床并不真正了解计算中使用的状态;它只需要调用一些东西,直到返回一个不可调用的东西。这是一个版本。
public class Trampoline {
/**
* State of a computation for a trampoline.
*
* @param <T> the type of value
*/
public interface TrampolineState<T> {
/**
* Returns whether the state is a finished state.
*
* @return whether the state is a finshed state
*/
boolean isFinished();
/**
* Returns the value, if this state is finished.
*
* @return the value
* @throws IllegalStateException if the state is not finished
*/
T getValue() throws IllegalStateException;
/**
* Returns the next state, if this state is not finished.
*
* @return the next state
* @throws IllegalStateException if the state is finished
*/
TrampolineState<T> getNext() throws IllegalStateException;
}
/**
* Executes a trampolined state and its successors until a finished state is
* reached, and then returns the value of the finished state.
*
* @param state the state
* @return the value
*/
public <T> T trampoline(TrampolineState<T> state) {
while (!state.isFinished()) {
state = state.getNext();
}
return state.getValue();
}
/**
* Returns the state for for sum computation.
*
* @param x the augend
* @param y the addend
* @return the state
*/
private TrampolineState<Integer> getSumTrampolineState(int x, int y) {
return new TrampolineState<Integer>() {
@Override
public boolean isFinished() {
return x == 0;
}
@Override
public Integer getValue() {
if (!isFinished()) {
throw new IllegalStateException();
}
return y;
}
@Override
public TrampolineState<Integer> getNext() {
if (isFinished()) {
throw new IllegalStateException();
}
return getSumTrampolineState(x - 1, y + 1);
}
};
}
/**
* Returns a sum, computed by a trampolined computation.
*
* @param x the augend
* @param y the addend
* @return the sum
*/
public int sum(int x, int y) {
return trampoline(getSumTrampolineState(x, y));
}
}
答案 3 :(得分:2)
这个词有两个独立的含义&#34;递归&#34;在这里使用。一个是语法 - 任何调用自身的函数在语法上(即语法方式)递归。
另一个是关于由给定代码片段编码的计算过程的基本行为 - 它是否在常量堆栈空间中运行(因此基本上是迭代的),或者不是(因此基本上是递归的)。
Scheme具有尾调用优化,因此您的代码实际上是
private int iterativeSum(int x, int y)
{
ITERATIVE_SUM:
System.out.println("x "+x+" y "+y);
if(x == 0)
{
goto RETURN;
}
--x; // return iterativeSum(--x, ++y);
++y;
goto ITERATIVE_SUM;
RETURN:
return y
}
等同于标准的while
循环,因为尾递归函数调用重用函数调用框。