При просмотре видео курса 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 ответа
Кажется, его больше интересует, как это выполняется, а не как написан код . Между этими двумя есть большая разница, но это совсем другой разговор (но, в частности, некоторые языки будут компилировать рекурсии как итерации, например).
В данном случае это итерация , когда вы удерживаете все состояние в одном месте и многократно работаете с этим одним фрагментом данных. Это рекурсия , когда у вас есть стек состояний и вы добавляете его в стек, а затем в конечном итоге свертываете стек обратно до ответа.
В своем примере в 31:00 он показывает, что это итерация, когда есть один лист бумаги, который фиксирует все состояние проделанной на данный момент работы, и любой человек может взять его и в конечном итоге дать окончательный ответ.
В примере с рекурсией 32:20 у Джо есть собственные заметки по проблеме, и он передает только заметки по части проблемы. Тогда у Гарри будет достаточно информации для его части проблемы, но вся проблема по-прежнему требует, чтобы Джо держался за свою собственную информацию, чтобы обрабатывать результаты Гарри, когда он получает их от Гарри.
У вас есть целая группа людей, и еще больше людей добавляются в стопку, пока у одного из них не возникнет проблема, достаточно простая, чтобы решить ее самостоятельно, и он может сразу же вернуть свой ответ, что означает, что у второго последнего человека теперь есть более простой проблема и теперь может возвращать свой ответ, и так далее, пока вся группа людей не превратится в одного последнего (первого) человека, который затем даст окончательный ответ.
В том смысле, что функция вызывает сама себя, она рекурсивна. Однако он имеет важный атрибут, заключающийся в том, что результат вызова зависит исключительно от результата вызова другой функции; никакие значения из текущего стека не нужны. Результат обеспечивается
return iterativeSum(--x, ++y);
Не из чего-то вроде
return iterativeSum(--x, ++y) + x;
Что потребовало бы «возврата» из рекурсивного вызова, выполнения каких-либо действий с результатом, а затем его возврата. Поскольку результат ничего не требует от текущего кадра стека, реализация (на некоторых языках, в зависимости от семантики) может исключить или повторно использовать текущий кадр стека. Это называется исключение хвостового вызова и является обязательным для некоторых языков, например 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));
}
}
Правильный батут
Как отметил Уилл Несс, батут на самом деле не знает о состояниях, используемых в вычислениях; ему просто нужно что-то вызвать, пока не будет возвращена не вызываемая вещь. Вот версия, которая это делает.
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));
}
}
Смотрите также:
Здесь используется два разных значения слова «рекурсивный». Один из них синтаксический - любая вызывающая функция сама по себе синтаксически (то есть синтаксически) рекурсивна.
Другой - о существенном поведении вычислительного процесса, закодированного данным фрагментом кода - независимо от того, выполняется ли он в постоянном пространстве стека (так что по сути итеративен) или нет (так что по существу рекурсивен).
Схема имеет оптимизацию хвостового вызова , поэтому ваш код на самом деле
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
, потому что хвостовой рекурсивный вызов функции повторно использует кадр вызова функции.
Я думаю, это основано на определениях в SICP. Вот соответствующий раздел. < / a> Короче говоря, рекурсивная функция может генерировать итеративный процесс , если рекурсивный вызов находится в хвостовой позиции: ни одно из текущих значений локальных переменных не нужно запоминать, и их пространство может быть очищено / повторно использовано (это несколько легче увидеть с LISP, где все является выражением, и можно увидеть, как размер выражения не увеличивается в итеративном процессе).
Рекурсивный процесс, напротив, еще не завершен после завершения рекурсивного вызова. Например, эта функция
private int recursiveSum(int x, int y)
{
if(x == 0)
{
return y;
}
return ++(recursiveSum(--x, y));
}
Сгенерирует рекурсивный процесс, так как необходимо выполнить дополнительную работу (++()
).
Реализует ли компилятор на самом деле оптимизацию хвостового вызова (TCO) - другой вопрос. AFAIK, на сегодняшний день JVM его не поддерживает. Функция, вызывающая себя в хвостовой позиции, в целом легко оптимизировать (как цикл). Сложность возникает, когда одна функция вызывает другую, а другая вызывает обратно первую функцию и т. Д.
Похожие вопросы
Связанные вопросы
Новые вопросы
java
Java — это высокоуровневый объектно-ориентированный язык программирования. Используйте этот тег, если у вас возникли проблемы с использованием или пониманием самого языка. Этот тег часто используется вместе с другими тегами для библиотек и/или фреймворков, используемых разработчиками Java.