I Liceum Ogólnokształcące w Jarosławiu - Informatyka

Rekurencja – Python

Rekurencja to sytuacja, w której funkcja w programie wywołuje samą siebie, żeby rozwiązać mniejszą wersję tego samego problemu.

Każda funkcja rekurencyjna ma dwa kluczowe elementy:

Jeśli nie ma warunku końcowego → program nigdy się nie zatrzyma (będzie wywoływał sam siebie w nieskończoność).


1. Czym jest rekurencja?

Intuicyjnie możesz myśleć o rekurencji jak o serii „zleceń”: funkcja nie liczy wszystkiego od razu, tylko mówi: „najpierw policz mi wersję prostszą, a ja dokończę”.

Przykład 1: Silnia

Silnia liczby n (zapis: n!) to iloczyn wszystkich liczb od 1 do n:

def silnia(n):
    if n == 0:        # 1. jeśli n to 0...
        return 1      # 2. ...zwracamy 1 i kończymy

    return n * silnia(n - 1)  # 3. inaczej: n razy silnia z (n-1)


# przykłady:
print(silnia(0))  # 1
print(silnia(4))  # 24
print(silnia(6))  # 720

Wyjaśnienie:

Gdy wywołamy silnia(4), w środku powstaje łańcuch:

4 * silnia(3)4 * (3 * silnia(2))4 * (3 * (2 * silnia(1))) → … aż dojdziemy do silnia(0), które zwraca 1 i wszystko się „rozplątuje”.

Przykład 2: Odliczanie w dół

def odliczanie(n):
    if n == 0:                 # 1. jeśli n to 0...
        print("Start!")        # 2. ...drukujemy komunikat
        return                 # 3. kończymy funkcję

    print(n)                   # 4. wypisz aktualną liczbę
    odliczanie(n - 1)          # 5. wywołaj siebie z n-1


odliczanie(5)

Wyjaśnienie:

Przykład 3: Suma liczb od 1 do n

def suma_do_n(n):
    if n == 1:          # 1. dla 1 wynik jest oczywisty
        return 1

    return n + suma_do_n(n - 1)


print(suma_do_n(5))  # 1+2+3+4+5 = 15

Wyjaśnienie:

W środku tworzy się wyrażenie: 5 + (4 + (3 + (2 + 1))).

Ćwiczenie 10.1


2. Rekurencyjna definicja ciągu

Ciąg rekurencyjny to taki, w którym każdy wyraz można obliczyć na podstawie poprzedniego (lub kilku poprzednich).

Przykład ciągu arytmetycznego:

Można to interpretować jako konto bankowe, na które co miesiąc dopłacasz stałą kwotę.

Przykład 1: Ciąg arytmetyczny (dodajemy stałą liczbę)

def wyraz_ciagu_arytm(a1, d, n):
    if n == 1:             # 1. pierwszy wyraz znamy
        return a1

    return wyraz_ciagu_arytm(a1, d, n - 1) + d


# a1 = 7, d = 3
for i in range(1, 6):
    print(f"a_{i} =", wyraz_ciagu_arytm(7, 3, i))

Wyjaśnienie:

Przykład 2: Ciąg geometryczny (mnożenie przez stały współczynnik)

Ciąg geometryczny:

def wyraz_ciagu_geom(a1, q, n):
    if n == 1:          # jeśli to pierwszy wyraz
        return a1

    return q * wyraz_ciagu_geom(a1, q, n - 1)


# przykład: a1=2, q=3
for i in range(1, 6):
    print(f"b_{i} =", wyraz_ciagu_geom(2, 3, i))

Wyjaśnienie:

Przykład 3: „Oszczędzanie” – saldo konta

Załóżmy:

def saldo(miesiac, poczatek=1000, wplata=200):
    if miesiac == 1:           # pierwszy miesiąc – znamy saldo
        return poczatek

    return saldo(miesiac - 1, poczatek, wplata) + wplata


for m in range(1, 7):
    print(f"Miesiąc {m}: {saldo(m)} zł")

Wyjaśnienie:

Ćwiczenie 10.2


3. Ciąg Fibonacciego

Ciąg Fibonacciego jest zdefiniowany tak:

Każda liczba to suma dwóch poprzednich. Występuje w naturze (spirale nasion, liczba płatków kwiatów), w informatyce i w sztuce.

Przykład 1: Podstawowa funkcja fib

def fib(n):
    if n == 0:
        return 0          # znamy od razu
    if n == 1:
        return 1          # też znamy

    return fib(n - 1) + fib(n - 2)


for i in range(8):
    print(f"F({i}) =", fib(i))

Wyjaśnienie:

Wywołania układają się w „drzewko zadań”: żeby policzyć F(5), musisz policzyć F(4) i F(3); żeby policzyć F(4), potrzebujesz F(3) i F(2) itd.

Przykład 2: Lista pierwszych k liczb Fibonacciego

def fib(n):
    if n == 0:
        return 0
    if n == 1:
        return 1
    return fib(n - 1) + fib(n - 2)


def fib_lista(k):
    lista = []
    for i in range(k):
        lista.append(fib(i))  # dopisujemy kolejne F(i) do listy
    return lista


print(fib_lista(8))   # [0, 1, 1, 2, 3, 5, 8, 13]

Wyjaśnienie:

Przykład 3: Rekurencyjne wypisywanie ciągu Fibonacciego

def wypisz_fib(n):
    if n == 0:
        print(0)
        return
    if n == 1:
        print(0)
        print(1)
        return

    # najpierw wypisz poprzednie liczby
    wypisz_fib(n - 1)

    # potem wypisz F(n)
    print(fib(n))


def fib(n):
    if n == 0:
        return 0
    if n == 1:
        return 1
    return fib(n - 1) + fib(n - 2)


wypisz_fib(6)

Wyjaśnienie:

Ćwiczenie 10.3


10.4. Rekurencyjny algorytm Euklidesa (NWD)

Algorytm Euklidesa oblicza największy wspólny dzielnik (NWD) dwóch liczb całkowitych.

Idea:

  1. Dzielimy liczbę a przez b, bierzemy resztę.
  2. Tworzymy nową parę liczb: (b, reszta).
  3. Powtarzamy, aż reszta będzie równa 0.
  4. Liczba, która wtedy zostanie, jest NWD.

10.4 – Przykład 1: Podstawowa funkcja nwd

def nwd(a, b):
    if b == 0:             # gdy dzielimy "na czysto"
        return a           # a jest największym dzielnikiem

    return nwd(b, a % b)   # w przeciwnym razie: zamień role liczb


print(nwd(48, 18))   # 6
print(nwd(84, 30))   # 6
print(nwd(30, 21))   # 3

Wyjaśnienie:

10.4 – Przykład 2: Skracanie ułamka przy użyciu NWD

def nwd(a, b):
    if b == 0:
        return a
    return nwd(b, a % b)


def skroc_ulamek(licznik, mianownik):
    d = nwd(licznik, mianownik)        # największy wspólny dzielnik
    return licznik // d, mianownik // d


print(skroc_ulamek(42, 56))   # (3, 4)
print(skroc_ulamek(36, 60))   # (3, 5)
print(skroc_ulamek(75, 105))  # (5, 7)

Wyjaśnienie:

10.4 – Przykład 3: Sprawdzenie, czy liczby są „względnie pierwsze”

Liczby są względnie pierwsze, gdy ich NWD = 1 (nie mają wspólnych dzielników poza 1).

def nwd(a, b):
    if b == 0:
        return a
    return nwd(b, a % b)


def wzglednie_pierwsze(a, b):
    return nwd(a, b) == 1


print(wzglednie_pierwsze(8, 15))   # True (względnie pierwsze)
print(wzglednie_pierwsze(12, 18))  # False (NWD=6)
print(wzglednie_pierwsze(7, 20))   # True

Wyjaśnienie:

Ćwiczenie 10.4


Podsumowanie

Rekurencja pozwala dzielić duże problemy na mniejsze i rozwiązywać je krok po kroku. Jest kluczowa w algorytmice, sortowaniu, przetwarzaniu drzew (np. folderów na dysku), operacjach na ciągach liczbowych oraz w wielu algorytmach matematycznych.

Rekurencja pomaga w… Przykłady
przetwarzaniu struktur „w głąb” katalogi i podkatalogi, drzewa genealogiczne
rozwiązywaniu dużych problemów przez dzielenie ich na mniejsze sortowanie szybsze niż proste metody „szkolne”, dzielenie przedziałów
modelowaniu natury i zjawisk fraktale, wzrost roślin, spirale w naturze
matematyce obliczeniowej NWD, silnia, ciągi liczbowe (np. Fibonacciego)

Im lepiej zrozumiesz rekurencję, tym łatwiej będzie Ci pracować z bardziej zaawansowanymi algorytmami w przyszłości.