Przeczytaj
Rekurencja a iteracja
Przypomnijmy sobie najważniejsze wiadomości na temat rekurencji i iteracji.
IteracjaIteracja to technika programowania polegająca na wielokrotnym wykonywaniu danego ciągu instrukcji. W programie iterację realizujemy za pomocą instrukcji iteracyjnych – czyli pętli. Instrukcje w pętli wykonywane są do momentu, gdy zostanie spełniony warunek wykonywania pętli.
W przypadku rekurencjirekurencji funkcja wywołuje samą siebie ze zmienionym parametrem. Parametr ten określa poziomy wywołań rekurencyjnych funkcji, to znaczy stopień zagłębienia się wywołań. Gdy zostaje spełniony warunek początkowy rekurencjiwarunek początkowy rekurencji, zwracana jest konkretna wartość. Dzięki temu ciąg wywołań rekurencyjnych ma swój koniec. Gdy funkcja się zakończy lub zwróci wartość, następuje powrót do poprzedniego poziomu (w miejsce po wywołaniu funkcji) i wykonywane są kolejne zapisane instrukcje.
Jakie są zatem wady i zalety tych dwóch metod - rekurencyjnej i iteracyjnej? Sprawdźmy, którą z nich zastosować w jakiej sytuacji.
Którą technikę wybrać?
Mimo że użycie rekurencji przy rozwiązywaniu niektórych problemów zapewnia krótki i czytelny kod, to algorytmy iteracyjne często są bardziej wydajne pod względem czasu i pamięci.
Stosowanie rekurencji w wielu przypadkach jest nieefektywne, często powtarzane są wiele razy te same obliczenia. Na przykład rekurencyjny algorytm obliczania elementu ciągu Fibonacciego (przedstawiony w e‑materiale Ciąg FibonacciegoCiąg Fibonacciego) ma złożoność , podczas gdy złożoność algorytmu iteracyjnego to . Wynika to z faktu, że w celu wyznaczenia wybranego elementu ciągu metodą rekurencyjną wielokrotnie są obliczane te same, poprzedzające go wartości:
Ponadto użycie rekurencji wymaga zajęcia dużej ilości pamięci. Jest to spowodowane koniecznością rezerwacji miejsca na zmienne, argumenty wywołania i wartości zwracane z każdym wywoływaniem funkcji. To sprawia, że program w wersji rekurencyjnej może być znacznie wolniejszy od tego napisanego metodą iteracyjną.
Aby porównać te dwie techniki programowania, napiszmy funkcje obliczające potęgę danej liczby, korzystając z metody iteracyjnej, a następnie rekurencyjnej. Zacznijmy od podejścia iteracyjnego.
Obliczanie potęgi metodą iteracyjną
Specyfikacja:
Dane:
wykładnik
– wykładnik potęgi, liczba naturalnapodstawa
– podstawa potęgi, liczba całkowita
Wynik:
wynik
– wynik potęgowania , liczba całkowita
Oto funkcja zapisana za pomocą pseudokodu w wersji iteracyjnej:
Parametrami przyjmowanymi przez funkcję są podstawa
, czyli element potęgowany, oraz jej wykładnik
.
Wynik potęgowania przechowywany jest w zmiennej wynik
. Inicjujemy ją wartością 1, dzięki czemu bezpośrednio otrzymamy wynik dla wykładnika równego 0.
Dopóki wykładnik
jest większy od 0, dopóty wynik
mnożymy przez podstawę. W ten sposób otrzymamy iloczyn tylu czynników podstawa
, ile wynosi wykładnik
.
Na koniec zwracany jest wynik
, w którym zapisany będzie wynik potęgowania.
Zaprezentowany schemat przedstawia podejście iteracyjne obliczania potęgi danej liczby dla podstawy równej 3 i wykładnika równego 4. Przedstawione tu zostały kolejne iteracje pętli.
Przedstawiony iteracyjny algorytm obliczania potęgi według definicji jest algorytmem o relatywnie niskiej złożoności czasowej. Istnieje jednak efektywniejsze rozwiązanie iteracyjne wykorzystujące schemat Hornera - szybkie potęgowanie liczb. Omówienie tego algorytmu znajdziesz w e‑materiale Algorytmy iteracyjne i liczbowe – potęgowanie liczbAlgorytmy iteracyjne i liczbowe – potęgowanie liczb.
Obliczanie potęgi metodą rekurencyjną
Specyfikacja:
Dane:
wykładnik
– wykładnik potęgi, liczba naturalnapodstawa
– podstawa potęgi, liczba całkowita
Wynik:
wynik
– wynik potęgowania , liczba całkowita
Oto zapisana za pomocą pseudokodu funkcja realizująca potęgowanie techniką rekurencyjną:
Funkcja rekurencyjnie()
przyjmuje dwa parametry - podstawa
oraz wykładnik
. Jeżeli wykładnik
jest równy 0, od razu zwracana jest wartość 1. Dzieje się tak dlatego, że niezależnie od podstawy liczba podniesiona do potęgi 0 daje wynik równy 1.
W przeciwnym wypadku mnożymy podstawę przez wynik potęgowania, którego wykładnik zmniejszony jest o 1. Oto matematyczny przykład dla 3Indeks górny 44:
Funkcja wywołuje rekurencyjnie samą siebie z drugim parametrem zmniejszonym o 1. Jeżeli parametr ten wynosi 0, spełniony zostaje warunek początkowy rekurencji. Funkcja nie wywołuje wtedy rekurencyjnie kolejnej funkcji, a zwraca konkretną wartość, w tym przypadku 1. Zwrócona wartość (pobrana ze stosu) zostaje przypisana jako wartość funkcji w miejscu jej wywołania i wykonywane są obliczenia, a przeznaczony na nią obszar pamięci na stosie zostaje zwolniony.
Szybkie potęgowanie liczb – definicja rekurencyjna
Istnieje szybszy sposób potęgowania liczb od potęgowania według definicji. Jest on oparty na schemacie Hornera.
Zwróćmy uwagę, że potęgowanie według definicji:
możemy obliczyć inną metodą. Przedstawia się ona następująco:
Stosując tę metodę, zamiast pięciu mnożeń wykonalibyśmy trzy. Oszczędność liczby mnożeń wydaje się niewielka, jednak dla większych wykładników znacząco skracamy czas przeprowadzania tego działania. Załóżmy, że znamy wartość potęgi . Wtedy chcąc wykonać działanie , dokonujemy tylko jednego mnożenia!
Obliczając tę potęgę zgodnie z powszechnie znaną definicją, znając wartość , musielibyśmy wykonać jeszcze 25 dodatkowych mnożeń.
Opierając się na powyższej analizie, możemy ustalić definicję rekurencyjną szybkiego potęgowania liczb () :
Na podstawie definicji konstruujemy algorytm rekurencyjny:
Specyfikacja problemu:
Dane:
n
– wykładnik potęgi, liczba naturalnaa
– podstawa potęgi, liczba całkowita różna od zera
Wynik:
wynik
– wynik potęgowania , liczba całkowita
Zaprezentowana metoda ma swoją wersję iteracyjną. Jej omówienie znajdziesz w e‑materiale Algorytmy iteracyjne i liczbowe – potęgowanie liczbAlgorytmy iteracyjne i liczbowe – potęgowanie liczb
Analiza złożoności czasowej
Złożoność czasowa algorytmu potęgowania liczb według następującej definicji:
wyrażona jako liczba wykonanych mnożeń wynosi - zarówno w wersji iteracyjnej jak i rekurencyjnej.
W przypadku algorytmu szybkiego potęgowania możemy zauważyć, że każde pojedyncze lub każde dwa kolejne wywołania rekurencyjne funkcji zmniejszają wykładnik dwukrotnie. Zatem zostanie wykonanych maksymalnie mnożeń. Złożoność algorytmu szybkiego potęgowania liczb wynosi więc .
Słownik
technika programowania polegająca na wielokrotnym powtarzaniu zapisanego ciągu instrukcji aż do spełnienia określonego warunku
technika programowania, w której funkcja wywołuje samą siebie aż do napotkania przypadku podstawowego, czyli takiego, którego nie da się rozłożyć na mniejsze problemy; przykład przypadku podstawowego dla obliczania piątego wyrazu ciągu Fibonacciego:
podana wprost wartość definiowanej wielkości, niezbędna, aby rekurencja się zakończyła