Znam ogólną koncepcję rekurencji. Zetknąłem się z koncepcją rekurencji ogona podczas studiowania algorytmu Quicksort. W tym filmie z algorytmem szybkiego sortowania z MIT o godzinie 18:30 profesor mówi, że jest to algorytm rekurencyjny ogona. Nie jest dla mnie jasne, co tak naprawdę oznacza rekurencja ogona.
Czy ktoś może wyjaśnić tę koncepcję odpowiednim przykładem?
Niektóre odpowiedzi udzielone przez społeczność SO tutaj .
Odpowiedzi:
Rekurencja ogona jest szczególnym przypadkiem rekurencji, w której funkcja wywołująca nie wykonuje już obliczeń po wykonaniu wywołania rekurencyjnego. Na przykład funkcja
jest ogonem rekurencyjnym (ponieważ ostatnia instrukcja jest wywołaniem rekurencyjnym), podczas gdy ta funkcja nie jest ogonem rekurencyjnym:
ponieważ wykonuje pewne obliczenia po powrocie wywołania rekurencyjnego.
Rekurencja ogona jest ważna, ponieważ można ją wdrożyć bardziej efektywnie niż rekurencja ogólna. Kiedy wykonujemy normalne wywołanie rekurencyjne, musimy wcisnąć adres zwrotny na stos wywołań, a następnie przejść do wywoływanej funkcji. Oznacza to, że potrzebujemy stosu wywołań, którego rozmiar jest liniowy na głębokości wywołań rekurencyjnych. Kiedy mamy rekurencję ogona, wiemy, że jak tylko wrócimy z wywołania rekurencyjnego, natychmiast również wrócimy, więc możemy pominąć cały łańcuch funkcji rekurencyjnych powracających i powrócić bezpośrednio do pierwotnego wywołującego. Oznacza to, że w ogóle nie potrzebujemy stosu wywołań dla wszystkich wywołań rekurencyjnych i możemy zrealizować ostateczne wywołanie jako zwykły skok, który oszczędza nam miejsce.
źródło
def recurse(x): if x < 0 return 1; for i in range 100{ (do calculations) recurse(x)}
Mówiąc prosto, rekurencja ogona jest rekurencją, w której kompilator może zastąpić wywołanie rekurencyjne poleceniem „goto”, więc skompilowana wersja nie będzie musiała zwiększać głębokości stosu.
Czasami zaprojektowanie funkcji rekurencyjnej ogona wymaga utworzenia funkcji pomocniczej z dodatkowymi parametrami.
Na przykład nie jest to funkcja rekurencyjna:
Ale jest to funkcja rekurencyjna:
ponieważ kompilator może przepisać funkcję rekurencyjną na nierekurencyjną, używając czegoś takiego (pseudokod):
Reguła kompilatora jest bardzo prosta: gdy znajdziesz „
return thisfunction(newparameters);
”, zamień go na „parameters = newparameters; goto start;
”. Można to jednak zrobić tylko wtedy, gdy wartość zwrócona przez wywołanie rekurencyjne zostanie zwrócona bezpośrednio.Jeśli wszystkie wywołania rekurencyjne w funkcji można zastąpić w ten sposób, jest to funkcja rekurencyjna.
źródło
Moja odpowiedź opiera się na wyjaśnieniach podanych w książce Struktura i interpretacja programów komputerowych . Bardzo polecam tę książkę informatykom.
Podejście A: liniowy proces rekurencyjny
Kształt procesu podejścia A wygląda następująco:
Podejście B: liniowy proces iteracyjny
Kształt procesu dla Podejścia B wygląda następująco:
Liniowy proces iteracyjny (podejście B) działa w stałej przestrzeni, mimo że proces ten jest procedurą rekurencyjną. Należy również zauważyć, że w tym podejściu ustawione zmienne określają stan procesu w dowolnym punkcie, a mianowicie.
{product, counter, max-count}
. Jest to również technika, dzięki której rekurencja ogona umożliwia optymalizację kompilatora.W podejściu A jest więcej ukrytych informacji, które interpreter utrzymuje, a mianowicie łańcuch odroczonych operacji.
źródło
Rekursja ogona jest formą rekurencji, w której wywołania rekurencyjne są ostatnimi instrukcjami funkcji (stąd pochodzi część ogona). Ponadto wywołanie rekurencyjne nie może składać się z odniesień do komórek pamięci przechowujących poprzednie wartości (odniesienia inne niż parametry funkcji). W ten sposób nie dbamy o poprzednie wartości i jedna ramka stosu wystarcza dla wszystkich wywołań rekurencyjnych; rekursja ogona jest jednym ze sposobów optymalizacji algorytmów rekurencyjnych. Inną zaletą / optymalizacją jest to, że istnieje prosty sposób na przekształcenie algorytmu rekurencyjnego ogona na równoważny, który używa iteracji zamiast rekurencji. Tak więc, algorytm szybkiego sortowania jest rzeczywiście rekurencyjny.
Oto wersja iteracyjna:
źródło