
Rekurencja to jedno z tych pojęć w programowaniu, które na początku może wydawać się skomplikowane. Funkcja wywołująca samą siebie? Brzmi jak sposób na stworzenie programu działającego w nieskończoność. W rzeczywistości rekurencja jest bardzo przydatną techniką, która pozwala rozwiązywać wiele problemów w prosty i czytelny sposób.
W tym wpisie wyjaśniam, co to jest rekurencja, jak działa funkcja rekurencyjna oraz dlaczego tak ważny jest warunek zakończenia. Pokażę też kilka przykładów rekurencji w Pythonie – od prostego odliczania, przez silnię, aż po ciąg Fibonacciego.
Co to jest rekurencja?
Rekurencja to sposób rozwiązywania problemu, w którym funkcja wywołuje samą siebie, ale dla prostszego przypadku.
Najprostszy przykład może wyglądać tak:
def odliczanie(n):
print(n)
if n > 0:
odliczanie(n - 1)
odliczanie(5)Program wyświetli:
Funkcja wykonuje część pracy i uruchamia samą siebie z mniejszą wartością. Gdy n osiąga zero, kolejne wywołanie już nie następuje.
Z czego składa się funkcja rekurencyjna?
Poprawnie zbudowana funkcja rekurencyjna ma dwa elementy:
- przypadek bazowy – określa, kiedy zatrzymać kolejne wywołania,
- krok rekurencyjny – wywołuje tę samą funkcję dla prostszego problemu.
def odliczanie(n):
print(n)
if n > 0: # przypadek bazowy: gdy n = 0, zatrzymujemy się
odliczanie(n - 1) # krok rekurencyjny: zmniejszamy problemKażde kolejne wywołanie otrzymuje mniejszą wartość n. To warunek konieczny, aby funkcja rzeczywiście doszła do przypadku bazowego.
Dlaczego warunek zakończenia rekurencji jest tak ważny?
Brak przypadku bazowego jest jednym z najczęstszych błędów przy nauce rekurencji.
def funkcja(n):
print(n)
funkcja(n - 1)Funkcja będzie próbowała wykonywać kolejne wywołania bez końca:
Python po pewnym czasie zakończy program błędem RecursionError: maximum recursion depth exceeded. Zanim napiszesz krok rekurencyjny, ustal zawsze: kiedy funkcja ma się zatrzymać?
Rekurencja w Pythonie - przykład z silnią
Jednym z najpopularniejszych przykładów zastosowania rekurencji jest obliczanie silni.
Problem naturalnie można więc zapisać rekurencyjnie:
def silnia(n):
if n == 0:
return 1
return n * silnia(n - 1)
print(silnia(5))Wynik: 120.
Więcej sposobów liczenia silni (pętla for, while, wersja w C++) i najczęstsze błędy znajdziesz w osobnym wpisie: silnia – co to jest i jak ją obliczyć.
Jak działa rekurencyjne obliczanie silni?
Przeanalizujmy dokładnie działanie silnia(5):
if n == 0:
return 1Funkcje są najpierw wywoływane, a potem zwracają wyniki w odwrotnej kolejności.
Rekurencja a stos wywołań funkcji
Każde wywołanie funkcji musi zostać zapamiętane przez program. Dla silnia(4) wygląda to tak:
Po osiągnięciu przypadku bazowego program zaczyna wracać:
Takie przechowywanie kolejnych wywołań odbywa się na tzw. stosie wywołań. Bardzo głęboka rekurencja może więc zużywać dużo pamięci.
Ciąg Fibonacciego z wykorzystaniem rekurencji
Kolejnym klasycznym przykładem jest ciąg Fibonacciego: 0, 1, 1, 2, 3, 5, 8, 13….
def fibonacci(n):
if n == 0:
return 0
if n == 1:
return 1
return fibonacci(n - 1) + fibonacci(n - 2)
print(fibonacci(7))Wynik: 13. Funkcja ma dwa przypadki bazowe – dla 0 i 1.
Obie wersje, rekurencyjną i iteracyjną, znajdziesz też z pseudokodem w encyklopedii Prymo: ciąg Fibonacciego rekurencyjnie oraz ciąg Fibonacciego iteracyjnie.
Czy rekurencja zawsze jest najlepszym rozwiązaniem?
Rekurencja nie zawsze jest najlepszym rozwiązaniem. Dla ciągu Fibonacciego wersja rekurencyjna jest czytelna, ale dla większych wartości wielokrotnie oblicza te same wyniki.
def fibonacci(n):
if n == 0:
return 0
if n == 1:
return 1
return fibonacci(n - 1) + fibonacci(n - 2)Wersja iteracyjna jest w tym przypadku wydajniejsza:
def fibonacci(n):
a = 0
b = 1
for i in range(n):
a, b = b, a + b
return aRekurencja nie jest „lepszą pętlą” – to po prostu inny sposób rozwiązania określonego typu problemu.
Rekurencja a iteracja - czym się różnią?
Iteracja powtarza instrukcje za pomocą pętli, np. for lub while. Rekurencja ponownie wywołuje tę samą funkcję.
Silnię możemy policzyć na oba sposoby:
def silnia(n):
if n == 0:
return 1
return n * silnia(n - 1)def silnia(n):
wynik = 1
for i in range(1, n + 1):
wynik *= i
return wynikOba programy zwracają ten sam wynik – różni się sposób dojścia do rozwiązania.
Kiedy warto stosować rekurencję?
Rekurencja sprawdza się szczególnie dobrze w problemach, które naturalnie można podzielić na mniejsze wersje tego samego problemu.
Przykłady to:
- przeszukiwanie struktur drzewiastych,
- przechodzenie po folderach i podfolderach,
- algorytmy typu dziel i zwyciężaj,
- przeszukiwanie grafów,
- niektóre algorytmy sortowania,
- obliczanie silni,
- generowanie kombinacji,
- rozwiązywanie problemów metodą backtracking.
Dobrym sposobem na rozpoznanie potencjalnego zastosowania rekurencji jest zadanie sobie pytania:
Czy rozwiązanie większego problemu mogę zbudować na podstawie rozwiązania jego mniejszej wersji?
Jeśli odpowiedź brzmi „tak”, rekurencja może być dobrym rozwiązaniem.
Rekurencja - najczęstsze błędy
Podczas pierwszych ćwiczeń z rekurencją najczęściej pojawiają się trzy problemy.
Brak przypadku bazowego
Funkcja wywołuje samą siebie bez końca.
Problem nie staje się prostszy
def funkcja(n):
if n == 0:
return
funkcja(n + 1)Przypadek bazowy istnieje, ale dla dodatniego n oddalamy się od zera zamiast się do niego zbliżać.
Nieprawidłowe użycie return
return n * silnia(n - 1)n * silnia(n - 1)W drugiej wersji wynik nie zostanie zwrócony przez funkcję.
Jak nauczyć się rekurencji?
Dobrym ćwiczeniem jest suma liczb od 1 do n. Dla 5 liczymy: 1 + 2 + 3 + 4 + 5.
def suma(n):
if n == 0:
return 0
return n + suma(n - 1)
print(suma(5))Wynik: 15. To dobre ćwiczenie, jeśli silnia wydaje Ci się jeszcze nieintuicyjna.
Rekurencja na maturze z informatyki
Na maturze z informatyki nie wystarczy przepisać gotowej funkcji rekurencyjnej z pamięci. Trzeba rozumieć, co dzieje się przy każdym wywołaniu.
Przed egzaminem radzę sprawdzić, czy potrafisz:
- przeanalizować działanie funkcji rekurencyjnej,
- wskazać przypadek bazowy,
- określić kolejność wywołań,
- obliczyć wynik dla konkretnego argumentu,
- porównać rozwiązanie rekurencyjne z iteracyjnym,
- samodzielnie zapisać prosty algorytm rekurencyjny.
Zadania w stylu maturalnym (analiza wywołań, wynik dla konkretnego argumentu) przećwiczysz w zadaniach z rekurencji na Prymo. Pełny zakres egzaminu opisuję w przewodniku po maturze z informatyki rozszerzonej.
Jeśli przygotowujesz się do matury i chcesz przećwiczyć rekurencję razem z resztą materiału (algorytmy, Python, bazy danych, zadania z arkuszy), zapraszam na korepetycje z informatyki. Uczą u nas ludzie, którzy sami zdali tę maturę, a pracujemy na konkretnych zadaniach, nie na gotowym kodzie do zapamiętania.
Podsumowanie - co warto zapamiętać?
Rekurencja polega na tym, że funkcja wywołuje samą siebie dla prostszej wersji tego samego problemu. Każda poprawna funkcja rekurencyjna musi mieć przypadek bazowy, który zatrzymuje kolejne wywołania.
Na początku rekurencja może wydawać się mniej intuicyjna niż zwykła pętla. Po rozpisaniu kolejnych wywołań na kartce szybko okazuje się jednak, że jej działanie jest bardzo logiczne.
Moja główna rada: nie ucz się gotowego kodu na pamięć. Lepiej zrozumieć trzy rzeczy:
- kiedy funkcja się zatrzymuje,
- jak problem jest zmniejszany,
- co dzieje się podczas powrotu z kolejnych wywołań.
Gdy zrozumiesz ten mechanizm na przykładzie silni, sumy liczb czy ciągu Fibonacciego, znacznie łatwiej będzie Ci analizować bardziej rozbudowane algorytmy rekurencyjne.



