Trzeba wyróżnić parę rzeczy:
@pytający
Rekurencja i jej zachowanie:
W miejscu, gdzie wywołujesz funkcję rekurencyjnie obecna przerywa działanie, aż do momentu kiedy wywołana funkcja się skończy i ew. zwróci wynik.
A więc:
void f( int x )
{
printf( "START %d\n", x );
if( x == 0 )
return;
f( x - 1 );
printf( "KONIEC %d\n", x );
}
wywołanie f(4) wypisze:
START 4
START 3
START 2
START 1
START 0
KONIEC 1
KONIEC 2
KONIEC 3
KONIEC 4
@reszta
Są dwie kwestie:
algorytm:
Generalnie rekurencję wymyślono długo przed jak wymyślono programowaniem i od początku było to wygodne narzędzie matematyczne...
Często bezpośrednie implementacje oryginalnych rekurencyjnych matematyczne definicji (np. takie jak w ciągu Fibonacciego) nie sa zbyt wydajne, ale dla innych problemów rozwiązania bazujące na rekurencji mogą być wydajniejsze.
Trudno mówić o wydajności rekurencji w algorytmicznym sensie -> jest to po prostu jedna z metod definiowania pojęć.
Jest przy tym czasem bardzo elegancka z kilku powodów:
-Niektóre algorytmy/struktury sa naturalnie rekurenyjne - drzewa, mergesort, quicksort, poniekąd dfs...
-Zapewnią pewną abstrakcje, co znacznie upraszcza dowody poprawności
-Nie musi jawnie wprowadzać pojęcia czasu i obliczeń (bardzo wygodne w niektórych przypadkach, np. obliczeń rozproszonych, patrz języki czysto funkcyjne)
implementacja:
Każdą iterację można zastąpić rekurencją. Rekurencję też można bez problemu zaimplementować za pomocą stosu bez jej wsparcia z poziomu języka (patrz FORTRAN i qsort2 od DejaVu). Języki czysto funkcyjne o których wspomniałem wcześniej nie wprowadzaja pojęć typowych dla programów imperatywnych (albo o częściach imperatywnych), opierają się na rekurencji i wyrażeniach lambda.
Co do wydajności to bardzo zależy od konkretnego przypadku. Generalnie każde wywołanie funkcji potrzebuje pewnej ilości pamięci (dokładna wartość zależy od kilku czynników) . Czasem można całkowicie się tego narzutu pozbyć lub go ograniczyć. (np. zwijając rekurencję ogonową do iteracji, co jest podstawowym sposobem tworzenia pętli w Scheme, a g++ -O2 również tę optymalizację wprowadza). W przypadku języków czysto funkcyjnych szczególnie skuteczne mogą być techniki optymalizacji rekurencji, gdyż można przyjąć kilka wygodnych, dodatkowych założeń.
Jeżeli mówimy o C++ to chociaż trzeba się zwykle postarać (tzn może nie jakoś bardzo mocno optymalizować, ale zastanowić się czego używamy -> stack z stla jest bardzo wolny(nie wiem czemu), jakby użyć zamiast tego vectora i najlepiej podać mu jaki ma być jego rozmiar, żeby nie musiał być przealokowywany, podjrzewam, że kod mógłby być szybszy) by napisać typowo rekurencyjny algorytm na własnym stosie, który by chodził szybciej. Natomiast wydajność pamięciowa prawie zawsze jest znacznie lepsza.
Na koniec przypominam, że optymalizacja algorytmu jest podstawą i wszelkiej poprawy wydajności należy szukać właśnie tam -> chociaż stałą trzeba uwzględniać w przypadku rzeczywistych programów(szczególnie dla małych danych), najpierw warto się zawsze zastanowić nad asymptotyczną złożonością algorytmu.