Вы пишете, что рекурсивная функция должна занимать меньше времени, поскольку она не создает новую переменную и выполняет меньше операций. Первое утверждение бессмысленно. Память для локальных переменных обычно выделяется одной операцией вычитания при входе в функцию, и это занимает незначительное время (это самое быстрое выделение, известное человеку). Второе утверждение просто неверно для вашей реализации на C ++. Поскольку вы измерили, что рекурсивная функция с вашим компилятором работает медленнее, из этого следует, что она делает больше, а не меньше.
А теперь почему.
Что ж, каждый вызов должен копировать адрес возврата и фактические аргументы в стеке. На это нужно время. Кроме того, для поддержки отладки и исключений каждый вызов функции обычно выполняет некоторую дополнительную работу, устанавливая красивый фрейм стека, по сути сохраняя информацию о том, каким был стек до вызова.
Однако рекурсивный вариант не должен быть медленнее. Но почти парадоксально, но вариант, который на практике может быть таким же быстрым, как итеративный, будет делать больше.Идея состоит в том, чтобы написать его так, чтобы компилятор мог преобразовать его в итеративную версию, то есть чтобы компилятор мог заменить рекурсивный вызов (который требует времени) с простым циклом.
Единственная проблема в том, что, насколько мне известно, очень немногие компиляторы 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.,
person
Cheers and hth. - Alf
schedule
09.10.2011