Rekurencja
Transkrypt
Rekurencja
Rekurencja Rekurencja, zwana także rekursją (ang. recursion, z łac. recurrere, przybiec z powrotem) to w logice, programowaniu i w matematyce odwoływanie się np. funkcji lub definicji do samej siebie. W logice wnioskowanie rekurencyjne opiera się na założeniu istnienia pewnego stanu początkowego oraz zdania (lub zdań) stanowiącego podstawę wnioskowania (przy czym, aby cały dowód był poprawny, zarówno reguła, jak i stan początkowy muszą być prawdziwe). Istotą rekurencji jest tożsamość dziedziny i przeciwdziedziny reguły wnioskowania, wskutek czego wynik wnioskowania może podlegać tej samej regule zastosowanej ponownie. Na prostym przykładzie: reguła: każdy ojciec jest starszy od swojego syna; każdy ojciec jest czyimś synem stan początkowy: jestem 22-letnim mężczyzną teza: ojciec ojca mojego ojca jest starszy ode mnie dowód: 1. mój ojciec jest starszy ode mnie 2. mój ojciec jest czyimś synem 3. ojciec mojego ojca jest starszy od mojego ojca 4. ojciec mojego ojca jest czyimś synem 5. itd. Na przykładzie zastosowań matematycznych poniższa definicja ciągu Fibonacciego jest rekurencyjna: , dla gdyż definiuje funkcję odwołując się w definicji do niej samej. Każda definicja rekurencyjna potrzebuje przynajmniej jednego przypadku bazowego (nie rekurencyjnego), w tym przypadku są to wartości dla 0 i1. W przeciwnym wypadku nigdy się nie zakończy. Dla przykładu, obliczenie wygląda następująco: Innym przykładem jest wyliczanie największego wspólnego dzielnika za pomocą algorytmu Euklidesa Rekurencja jest podstawową techniką wykorzystywaną w funkcyjnych językach programowania. Należy jednak zachować ostrożność przy używaniu rekurencji w rzeczywistych programach. Ryzyko istnieje szczególnie przy przetwarzaniu dużej ilości głęboko zagnieżdżonych danych. Jeśli program nie jest w rzeczywistości rekurencyjny, to rekurencja może dramatycznie zwiększyć złożoność obliczeniową. Ponadto rekurencja zawsze zwiększa pamięciowe zapotrzebowanie programu (chyba że zostanie użyta możliwa w pewnych przypadkach optymalizacja zwana rekursją ogonową), gdyż wymaga ona zapamiętania m.in. adresów powrotu, pozwalających programowi "zorientować się" do którego miejsca ma wrócić po zakończeniu jednego z wywołań rekurencyjnych. Inną częstą wadą rekurencji jest kompletnie niezależne rozwiązywanie podproblemów, tak, że czasem jeden problem jest rozwiązywany w kilku miejscach rozwinięcia rekurencji, np. w powyższym przykładzie obliczania niepotrzebnie jest dwukrotnie obliczana wartość Takie problemy nie pojawiają się przy drugim z przykładów. Niezaprzeczalną zaletą rekurencji jest przejrzystość programów, które z niej korzystają. Rekurencja ogonowa Rekurencja ogonowa, zwana też Tail call lub prawostronną jest rodzajem rekurencji, w której ostatnia operacja wykonywana przez funkcję to rekurencyjne wywołanie samej siebie lub zwrócenie końcowego wyniku[1]. Taka funkcja może zostać łatwo zamieniona na iterację, zarówno ręcznie, jak i automatycznie, co redukuje wielkość stosu oraz zwiększa wydajność. Ta technika iteracyjnego wykonywania obliczeń jest powszechna w programowaniu funkcyjnym promującym używanie rekurencji, która w przeciwnym wypadku zajęłaby cały dostępny stos. Opis Wywołanie funkcji z danego kawałka kodu oznacza dla komputera wykonanie skoku w inne miejsce. Zanim go wykona, musi zapamiętać m.in. adres powrotu, wartości rejestrów oraz zmiennych. Najczęściej wykorzystywana jest do tego struktura danych zwana stosem, w której przechowywane są informacje o wszystkich aktualnie wykonywanych funkcjach zgodnie z kolejnością ich zagnieżdżenia, przy czym każda funkcja zajmuje pojedynczą ramkę stosu. Każdy program rezerwuje na potrzeby stosu ściśle określoną ilość pamięci, co narzuca ograniczenie na maksymalną ilość zagnieżdżonych wywołań funkcji. W przypadku niektórych funkcji ostatnia wykonywana w nich operacja przed zwróceniem wyniku to wywołanie innej funkcji (w szczególności - siebie samej), bez wykonywania żadnych dodatkowych obliczeń[2]. W tym przypadku zapamiętywanie adresu powrotu oraz stanu na stosie jest zbędne, ponieważ nie jest on już potrzebny. Takie wywołanie nazywa się wywołaniem ogonowym (ang. tail call)[3] i może zostać zoptymalizowane zastąpieniem wywołania przez zwykłą instrukcję skoku. Optymalizacja wywołań ogonowych może być stosowana do zwykłych, nierekurencyjnych funkcji, pozwalając na zaoszczędzenie odrobiny czasu i pamięci, a także zmniejszając zapotrzebowanie na stos. Funkcje rekurencyjne mogą wywoływać siebie bardzo dużą liczbę razy, a w szczególnych przypadkach potencjalnie w nieskończoność. Wielkość stosu jest fizycznie ograniczona z góry przez zasoby komputera. Optymalizacja ogonowa sprawia, że rekurencyjne wywołanie może korzystać z już istniejącej ramki, przez co zapotrzebowanie na stos maleje z liniowego O(n) do stałego O(1).