Przeczytaj
Na czym polega rekurencja?
Istotą rekurencjirekurencji jest wykorzystanie funkcji, która – aby rozwiązać pewien problem – wywołuje samą siebie, aż do momentu, gdy zadanie zostanie wykonane. Funkcja ta może być typu voidvoid
, gdy wywoływana funkcja nie zwraca wartości do funkcji wywołującej, lecz np. wypisuje ją do konsoli. Najczęściej jednak funkcja rekurencyjna oblicza oraz zwraca wartość.
Aby mechanizm rekurencji działał poprawnie, potrzebny jest przynajmniej jeden warunek, po spełnieniu którego funkcja nie zostanie wywołana po raz kolejny, lecz zakończy działanie (a także zwróci wartość – o ile miała ją zwracać). Jeżeli taki warunek się nie pojawi, funkcja będzie wywoływać się w nieskończoność – może to spowodować zawieszenie programu – jak w przypadku nieskończonej pętlinieskończonej pętli – lub przepełnienie stosu. Nie otrzymamy wówczas rozwiązania problemu.
Z każdym kolejnym wywołaniem funkcji rekurencyjnej przynajmniej jeden z jej argumentówargumentów musi zmienić wartość. Zmiany argumentów muszą prowadzić do przypadku bazowego. Wartości argumentów nie mogą powtórzyć się w następnych wywołaniach, z wyjątkiem argumentów, których wartości przerywają dany ciąg wywołań rekurencyjnych. Wartości argumentów stanowią wtedy przypadek podstawowy rekurencji. Jeżeli złamiemy chociaż jedną z przedstawionych zasad, funkcja rekurencyjna będzie wywoływać się bez końca. Kolejne wywołanie zostaje zapisane na stosie, więc gdy funkcja będzie uruchamiana w nieskończoność, w pewnym momencie stos przepełni się, a program zakończy działanie.
Każde rekurencyjne wywołanie funkcji zatrzymuje wykonywanie funkcji bieżącej. Czeka ona, aż funkcja przez nią wywołana zakończy działanie i zwróci wartość. Przy wywoływaniu kolejnych funkcji rekurencyjnych poprzednie funkcje także zatrzymują się i czekają, aż funkcje przez nie wywołane zwrócą obliczoną wartość.
Opisany proces można przedstawić graficznie przy użyciu drzewa wywołań rekurencyjnych.
Z przedstawionej grafiki wynika, że pierwszym wywołaniem funkcji rekurencyjnej Funkcja
było: Funkcja(a)
. Ta z kolei w pierwszej kolejności wywołała rekurencyjnie Funkcja(b)
. A zatem wywołanie Funkcja(a)
czeka, aż wywołana Funkcja(b)
zwróci wartość.
Kolejnym krokiem było wywołanie Funkcja(c)
. Wywołanie funkcji z takim argumentem nie spowodowało kolejnego wywołania rekurencyjnego, lecz zwrócenie wartości. Zobrazowane to zostało przy użyciu czerwonej strzałki z oznaczeniem 3. Jednak Funkcja(b)
wywołała nie jedno, lecz dwa wykonania rekurencyjne. Drugim jest Funkcja(d)
, które podobnie jak Funkcja(c)
, zamiast wywoływać dalsze wykonania rekurencyjne, zwróciła wartość.
Kolejny krok przedstawia czerwona strzałka z numerem 6. Wywołanie Funkcja(b)
zwraca wartość do Funkcja(a)
. Następnym krokiem jest wywołanie kolejno: Funkcja(e)
→ Funkcja(f)
→ Funkcja(g)
, po czym ostatnie wywołanie zwraca wartość do poprzedniego i w ten sposób dochodzimy do pierwszego wywołania Funkcja(a)
, które finalnie zwraca wartość.
Rekurencja – przykład
Zanim przejdziemy do ciągu Fibonacciego, napiszmy prosty program wykorzystujący mechanizm rekurencji. Program ten zwróci sumę wszystkich liczb naturalnych mniejszych lub równych liczbie podanej jako argument.
Rozpoczynamy od zdeklarowania funkcji.
Ponieważ funkcja ma obliczyć sumę liczb naturalnych, typem zwracanych danych jest int
. Parametr n
będzie górną granicą składników sumy.
Zaczniemy od zapisania warunku, po spełnieniu którego funkcja zakończy działanie i zwróci wartość. Warunek będzie dotyczył najmniejszej możliwej wartości parametru n
. Zakładamy, że liczby naturalne zaczynają się od 1. Ile wynosi suma liczb naturalnych mniejszych lub równych 1? Jest to 1. Skonstruujmy więc instrukcję warunkowąinstrukcję warunkową, która będzie odpowiedzialna za działanie programu w przypadku, gdy wartość n
wynosi 1:
Kolejnym i ostatnim krokiem jest rekurencyjne wywołanie funkcji.
Ponieważ chcemy uzyskać sumę liczb naturalnych, a różnica między kolejnymi liczbami naturalnymi zawsze wynosi 1, za każdym razem, gdy wywołujemy kolejną funkcję, odejmujemy od jej argumentu wartość 1. Do wyniku tego wywołania dodajemy wartość aktualnego argumentu i ta suma zostaje przekazana funkcji, która wywołała bieżącą funkcję.
Aby lepiej zrozumieć działanie programu, sprawdźmy, jak się on zachowa, gdy jako argument podamy wartość 3.
Sprawdzamy, czy 3 jest równe 1. Nie jest to prawdą, więc pomijamy instrukcję warunkową i przechodzimy do wywołania rekurencyjnego. Liczbę 3 dodajemy do wartości zwróconej przez sumaLiczbNaturalnych(2)
. Funkcja przerywa działanie i przechodzi do funkcji, którą sama wywołała, czyli sumaLiczbNaturalnych(2)
.
Jak wiemy, 2 również nie jest równe 1. Pomijamy warunek i ponownie zatrzymujemy wykonywanie tej funkcji. Zwróćmy uwagę na funkcję, która została wywołana, czyli sumaLiczbNaturalnych(1)
.
W tym przypadku instrukcja warunkowa nie zostanie pominięta. Skoro 1 == 1
, nie wywołujemy już funkcji sumaLiczbNaturalnych()
, lecz zwracamy wartość 1. Przekazujemy ją funkcji, która wywołała bieżącą funkcję.
Ta z kolei otrzymała wartość, na którą czekała. Może więc dodać ją do swojego argumentu i zwrócić obliczony wynik funkcji, która wywołała tę wartość.
Wywołana pierwotnie funkcja zwraca wartość 6, która jest sumą wszystkich liczb naturalnych mniejszych lub równych 3.
Ciąg Fibonacciego
Definicja ciągu Fibonacciego wygląda następująco: początkowymi elementami ciągu są 0 oraz 1, natomiast każdy kolejny element jest sumą dwóch poprzednich. Trzecim elementem ciągu Fibonacciego będzie zatem , czwartym , piątym itd. Indeksy elementów zaczynają się od 0, podobnie jak indeksy tablicy w języku Java. Pierwszy element – o wartości 0 – ma indeks n = 0
.
Rekurencja
Napisz w języku Java program, który zwróci wyraz ciągu Fibonacciego o indeksie n
. Zastosuj mechanizm rekurencyjny. Przetestuj jego działanie dla n = 3
.
Ciąg Fibonacciego to ciąg liczb, w którym pierwszy wyraz jest równy 0, drugi jest równy 1, a każdy następny jest sumą dwóch poprzednich. Ciąg ten można przedstawić za pomocą rekurencyjnego wzoru funkcji , obliczającej element ciągu Fibonacciego:
Specyfikacja:
Dane:
n
– indeks elementu ciągu; liczba naturalna
Wynik:
Program oblicza wyraz ciągu Fibonacciego o indeksie n
.
Porównaj swoje rozwiązanie z prezentacją.
Iteracja
Napiszemy program, który zwróci element ciągu Fibonacciego o indeksie n
. Zastosujemy iterację. Zaczniemy ponownie od utworzenia funkcji.
Wybieramy typ long
, ponieważ wartości kolejnych wyrazów ciągu Fibonacciego rosną wykładniczo.
Parametr n
funkcji będzie indeksem elementu ciągu, który ma być obliczony.
Zapisujemy warunki, po których spełnieniu zostanie zatrzymane działanie funkcji i zwróci ona wartość. Wiemy, że pierwszy wyraz ciągu Fibonacciego – zatem wyraz o indeksie 0 – ma wartość 0. Natomiast drugi wyraz – czyli wyraz o indeksie 1 – wynosi 1. Zapiszemy odpowiednie instrukcje warunkowe.
Musimy brać również pod uwagę przypadek, gdy wartość parametru n jest większa niż 1. W takiej sytuacji każdy kolejny element jest sumą dwóch poprzednich. Dla dalszego działania funkcji potrzebujemy zmiennych pomocniczych. Inicjalizujemy trzy zmienne pomocnicze: a
, b
(oznaczające wartości dwóch początkowych wyrazów ciągu) oraz i
(indeks kolejnego elementu ciągu Fibonacciego). Przypisujemy im odpowiednio wartości 0, 1 i 2.
Zapisujemy pętlę while, która będzie wykonywać się tak długo, dopóki wartość zmiennej i
jest mniejsza od wartości zmiennej n
lub jej równa.
W każdym obiegu pętli while
najpierw obliczamy i
-ty wyraz ciągu, czyli sumę dwóch poprzednich wyrazów ciągu. Wynik przypisujemy do zmiennej suma
. Następnie zmiennej a
przypisujemy wartość zmiennej b
, a zmiennej b
wartość zmiennej suma
. Na końcu zwiększamy wartość zmiennej i
o 1.
Po zakończeniu pętli wartość zmiennej b
jest zwracana jako wynik działania funkcji.
Oto cały kod programu:
Słownik
element składni w określonym języku programowania, który w wyniku wywołania podprogramu zostaje utożsamiony (skojarzony) z określonym parametrem podprogramu
funkcja, która nie zwraca żadnej wartości
element języka programowania sterujący działaniem programu; pozwala wykonać różne instrukcje w zależności od tego, czy zdefiniowane przez programistę wyrażenie logiczne jest prawdziwe czy nieprawdziwe
pętla, która nigdy nie spełnia warunku zakończenia; pętla działająca w nieskończoność
element składni w określonym języku programowania; umożliwia komunikację pomiędzy podprogramem (funkcją) wywołanym a programem wywołującym; parametry określa się w nagłówku podprogramu (przy jego definicji)
sytuacja, w której funkcja wywołuje samą siebie, aż do napotkania przypadku podstawowego