Rekurencja
bZIK
Ostatnie wpisy bZIK (zobacz wszystkie)
- Cały zarząd ZFPR podał się do dymisji. Branża zaskoczona - 11 kwietnia 2021
- Żołnierze Ukraińskiej Gwardii Narodowej będą szkoleni przez oficerów amerykańskich - 11 kwietnia 2021
- KRLD będzie nadal ulepszać swoją broń jądrową. Prace nad nowym atomowym okrętem podwodnym zakończone - 11 kwietnia 2021
- Biznes na wizach dla Ukraińców. Przetarg wart 170 mln zł i kontrowersyjny list - 11 kwietnia 2021
- Wadliwe liczniki prądu. Tauron żąda nawet kilku tysięcy dopłaty. Wezwania za kilka lat wstecz - 11 kwietnia 2021
Rekurencja, zwana także rekursją (ang. recursion, z łac. recurrere, przybiec z powrotem) – 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:
- mój ojciec jest starszy ode mnie
- mój ojciec jest czyimś synem
- ojciec mojego ojca jest starszy od mojego ojca
- ojciec mojego ojca jest czyimś synem
- itd.
Na przykładzie zastosowań matematycznych poniższa definicja ciągu Fibonacciego jest rekurencyjna:
-
- {\displaystyle \mathrm {fib} (0)=0,}
- {\displaystyle \mathrm {fib} (1)=1,}
- {\displaystyle \mathrm {fib} (n)=\mathrm {fib} (n-1)+\mathrm {fib} (n-2)} dla {\displaystyle n\geqslant 2,}
lub inaczej:
-
- {\displaystyle {\text{fib}}(n)={\begin{cases}0&{\text{dla }}n=0,\\1&{\text{dla }}n=1,\\{\text{fib}}(n-1)+{\text{fib}}(n-2)&{\text{dla }}n>1.\end{cases}}}
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 i 1. W przeciwnym wypadku nigdy się nie zakończy.
Dla przykładu, obliczenie {\displaystyle \mathrm {fib} (4)} wygląda następująco:
-
- {\displaystyle \mathrm {fib} (4)=\mathrm {fib} (3)+\mathrm {fib} (2)=(\mathrm {fib} (2)+\mathrm {fib} (1))+(\mathrm {fib} (1)+\mathrm {fib} (0))}{\displaystyle {}=((\mathrm {fib} (1)+\mathrm {fib} (0))+\mathrm {fib} (1))+(\mathrm {fib} (1)+\mathrm {fib} (0))=((1+0)+1)+(1+0)=3}
Innym przykładem jest wyliczanie największego wspólnego dzielnika za pomocą algorytmu Euklidesa:
- {\displaystyle \operatorname {gcd} (0,n)=n,}
- {\displaystyle \operatorname {gcd} (k,n)=\operatorname {gcd} (n\ {\text{mod }}k,k)} dla {\displaystyle k>0} {\displaystyle (n\ {\text{mod }}k} oznacza tu resztę z dzielenia {\displaystyle n} przez {\displaystyle k).}
lub inaczej:
-
- {\displaystyle {\text{gcd}}(k,n)={\begin{cases}n&{\text{dla }}k=0;\\{\text{gcd}}(n{\text{ mod }}k,k)&{\text{dla }}k>0.\end{cases}}}
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 poważnie zwiększyć złożoność obliczeniową. Ponadto zawsze zwiększa ona zapotrzebowanie programu na pamięć operacyjną (chyba że zostanie użyta możliwa w pewnych przypadkach optymalizacja zwana rekurencją ogonową), gdyż wymaga 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 {\displaystyle \mathrm {fib} (4)} niepotrzebnie jest dwukrotnie obliczana wartość {\displaystyle \mathrm {fib} (2)} (porównaj: programowanie dynamiczne). Takie problemy nie pojawiają się przy drugim z przykładów. Jednak, niezaprzeczalną zaletą rekurencji jest przejrzystość programów, które z niej korzystają.
Jedną z typowych sytuacji, w których stosuje się rekurencję jest przeszukiwanie struktury danych w postaci nieregularnego drzewa, np. plików XML. Funkcja, która sprawdza czy w danym obiekcie XML istnieje element o określonej zawartości mogłaby wyglądać następująco (tutaj w PHP przy użyciu klasy SimpleXML):
function find_text($text, $tree) {
// sprawdź zawartość aktualnego elementu
if ($text == (string)$tree) {
return true;
}
// sprawdź wszystkie jego dzieci
foreach ($tree as $node) {
// tutaj następuje wywołanie rekurencyjne
if (find_text($text, $node)) {
return true;
}
}
// nic nie znaleziono
return false;
}
W językach, w których nie ma możliwości użycia rekurencji, a w których funkcje są typem pierwszoklasowym, istnieje możliwość dodania obsługi rekurencji poprzez kombinator Y. Przykładem może być rachunek lambda.
źródło