迭代函数可以调用自身吗?

时间:2016-09-06 00:15:47

标签: java recursion iteration lisp computer-science

在查看以下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);
}

4 个答案:

答案 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循环,因为尾递归函数调用重用函数调用框。