Вы пишете, что рекурсивная функция «должна быть менее трудоемкой, поскольку она не создает новую переменную и выполняет меньше операций».Первое утверждение довольно бессмысленно.Память для локальных переменных обычно выделяется одной операцией вычитания при входе в функцию, и это занимает незначительное время (это самое быстрое распределение, известное человеку).Второе утверждение просто ложно для вашей реализации C ++.Поскольку вы измерили, что рекурсивная функция с вашим компилятором медленнее, из этого следует, что она делает больше, а не меньше.
Теперь, почему.
Ну, каждый вызов должен копироватьадрес возврата и фактические аргументы в стеке.Это требует времени.Кроме того, для поддержки отладки и исключений каждый вызов функции обычно выполняет некоторую дополнительную работу, устанавливая красивый кадр стека , по существу сохраняя информацию о том, каким был стек до вызова.
Aоднако рекурсивный вариант не должен быть медленнее.Но, как это ни парадоксально, вариант, который на практике может быть таким же быстрым, как итерационный, будет казаться более эффективным ... Идея состоит в том, чтобы написать его так, чтобы компилятор мог преобразовать его в итерационную версию, то есть так, чтобы компиляторзамените рекурсивный вызов (который требует времени) простым циклом.
Единственная проблема заключается в том, что, насколько мне известно, очень мало, если какой-либо компилятор C ++ выполняет такую оптимизацию.: - (
Однако, для полноты, идея состоит в том, чтобы убедиться, что существует только один рекурсивный вызов, и что это самое последнее, что происходит. Это называется хвостовая рекурсия . Ваштекущий рекурсивный код,
double factorial( int n )
{
if( n < 2 ) { return 1; }
return n*factorial( n-1 );
}
не является хвостово-рекурсивным, потому что после рекурсивного вызова происходит умножение на n
.
Чтобы сделать его хвост-рекурсивным, вы можете передать в качестве аргументовинформация, необходимая для выполнения того, что должно быть сделано в конце, здесь *n
. Информация, необходимая для этого, - значение n
плюс тот факт, что это должно быть сделано. Это означает введение вспомогательной функции с подходящим формальным аргументом:
double factorialScaledBy( double m, int n )
{
if( n < 2 ) { return m*1; }
// Same as "n*factorialScaledBy( m, n-1 )", but tail-recursive:
return factorialScaledBy( n*m, n-1 );
}
double factorial( int n )
{
return factorialScaledBy( 1, n );
}
Теперь достаточно умный компилятор может заметить, что больше ничего не происходит при выполнении функции после рекурсивного вызова, поэтому локальные переменные не используются, поэтому их можно просто повторно использовать для рекурсивного вызова, что поэтомуможет быть реализовано как просто имитация передачи аргумента плюс переход к вершине функции, т.е.петля.
Ура и hth.,