Idea rozwiązania rekurencyjnego

Mechanizm rekurencji polega na wykorzystaniu funkcji, która wywołuje samą siebie aż do momentu, gdy przeznaczone jej do rozwiązania zadanie zostanie wykonane. Funkcja taka może być typu void, ale może również zwracać obliczoną wartość.

Zobaczmy, jak wygląda funkcja rekurencyjna, której zadaniem jest wypisanie wszystkich liczb naturalnych dodatnich mniejszych lub równych od podanego parametru.

Specyfikacja problemu:

Dane:

  • n – liczba naturalna dodatnia

Wynik:

Program wypisuje malejący ciąg liczb naturalnych dodatnich mniejszych lub równych n.

Linia 1. kratka include otwórz nawias ostrokątny iostream zamknij nawias ostrokątny. Linia 2. using namespace std średnik. Linia 4. void wypiszNaturalne otwórz nawias okrągły int n zamknij nawias okrągły otwórz nawias klamrowy. Linia 5. if otwórz nawias okrągły n otwórz nawias ostrokątny 1 zamknij nawias okrągły otwórz nawias klamrowy. Linia 6. return średnik. Linia 7. zamknij nawias klamrowy. Linia 9. cout otwórz nawias ostrokątny otwórz nawias ostrokątny n otwórz nawias ostrokątny otwórz nawias ostrokątny endl średnik. Linia 10. wypiszNaturalne otwórz nawias okrągły n minus 1 zamknij nawias okrągły średnik. Linia 11. zamknij nawias klamrowy.

Ponieważ zadaniem funkcji jest wypisanie liczb na standardowe wyjście, nie ma potrzeby, aby zwracała ona jakąkolwiek wartość. Zdefiniowaliśmy zatem funkcję typu void. Parametr funkcjiparametr funkcjiParametr funkcji n przechowuje wartość największej liczby naturalnej, którą program musi wypisać.

Jako pierwsza w funkcji pojawia się instrukcja warunkowainstrukcja warunkowainstrukcja warunkowa, która zatrzyma działanie funkcji, bez wywołania kolejnej instancji, jeżeli parametr n osiągnie wartość mniejszą niż jeden. Warunków powodujących zakończenie działania funkcji rekurencyjnej może być więcej niż jeden.

Zaraz pod instrukcją warunkową wypisujemy wartość parametru n, a następnie wywołujemy funkcję rekurencyjną z argumentemargument funkcjiargumentem zmniejszonym o 1.

Prześledźmy działanie funkcji, gdy wywołamy ją z argumentem n równym 3.

Linia 1. if otwórz nawias okrągły 3 otwórz nawias ostrokątny 1 zamknij nawias okrągły otwórz nawias klamrowy. Linia 2. return średnik. Linia 3. zamknij nawias klamrowy. Linia 5. cout otwórz nawias ostrokątny otwórz nawias ostrokątny 3 otwórz nawias ostrokątny otwórz nawias ostrokątny endl średnik. Linia 6. wypiszNaturalne otwórz nawias okrągły 3 minus 1 zamknij nawias okrągły średnik.

Czy 3 jest mniejsze niż 1? Nie. Dlatego pomijamy instrukcje warunkową i wypisujemy wartość 3, a następnie wywołujemy tę samą funkcję z argumentem zmniejszonym o 1, czyli n = 2.

Linia 1. if otwórz nawias okrągły 2 otwórz nawias ostrokątny 1 zamknij nawias okrągły otwórz nawias klamrowy. Linia 2. return średnik. Linia 3. zamknij nawias klamrowy. Linia 5. cout otwórz nawias ostrokątny otwórz nawias ostrokątny 2 otwórz nawias ostrokątny otwórz nawias ostrokątny endl średnik. Linia 6. wypiszNaturalne otwórz nawias okrągły 2 minus 1 zamknij nawias okrągły średnik.

Liczba 2 nie jest mniejsza od 1, więc polecenie zapisane w instrukcji warunkowej nadal nie zostaje wykonane. Wpisujemy więc liczbę 2 i wywołujemy funkcję z argumentem równym 1.

Linia 1. if otwórz nawias okrągły 1 otwórz nawias ostrokątny 1 zamknij nawias okrągły otwórz nawias klamrowy. Linia 2. return średnik. Linia 3. zamknij nawias klamrowy. Linia 5. cout otwórz nawias ostrokątny otwórz nawias ostrokątny 1 otwórz nawias ostrokątny otwórz nawias ostrokątny endl średnik. Linia 6. wypiszNaturalne otwórz nawias okrągły 0 zamknij nawias okrągły średnik.

Czy 1 jest mniejsze niż 1? Ponownie nie, więc wypisujemy wartość 1 i wywołujemy funkcję po raz kolejny, tym razem z argumentem równym 0.

Linia 1. if otwórz nawias okrągły 0 otwórz nawias ostrokątny 1 zamknij nawias okrągły otwórz nawias klamrowy. Linia 2. return średnik. Linia 3. zamknij nawias klamrowy.

Ponieważ liczba 0 jest mniejsza niż 1, wyrażenie logiczne w instrukcji warunkowej przybierze wartość true. W rezultacie zostanie wykonane polecenie return, które zakończy działanie funkcji. Wynikiem działania programu będą liczby 3, 2, 1.

Jak widać, każde wywołanie funkcji zatrzymuje pracę funkcji bieżącej. Czeka ona, aż funkcja przez nią wywołana zakończy działanie (i zwróci wartość, jeśli miałaby ją zwracać). Przy wywoływaniu kolejnych funkcji rekurencyjnych dzieje się tak samo.

Jeżeli wywołana funkcja rekurencyjna spełni warunek zakończenia pracy i – gdy tego oczekiwano – zwróci wartość zamiast wywoływać siebie samą, funkcja, która ją wywołała, może podjąć działanie. Zwraca ona wartość poprzedniczce, która również podejmuje działanie, zwraca wartość funkcji ją wywołującej i tak dalej. Proces ten trwa do momentu, gdy pracę wznowi pierwsza, wywołana przez program funkcja. Zwraca ona ostateczny wynik realizacji algorytmu rekurencyjnego.

Aby opisany mechanizm działał poprawnie, potrzebny jest co najmniej jeden warunek, po spełnieniu którego funkcja nie zostanie wywołana po raz kolejny, lecz zakończy się (oraz ewentualnie zwróci wartość). Jeżeli taki warunek się nie pojawi, funkcja będzie wywoływać się w nieskończoność, co spowoduje zawieszenie programu lub błąd przepełnienia stosu.

Przy każdym kolejnym wywołaniu funkcji jej parametr lub jeden z parametrów muszą zmienić wartość. Nie może też dojść do sytuacji, w której wartość parametru lub parametrów powtórzą się między wywołaniami. Jeżeli tak się stanie, program może nigdy nie zakończyć działania.

Ciąg Fibonacciego

Ciąg Fibonacciegociąg FibonacciegoCiąg Fibonacciego to ciąg liczb naturalnych, w którym pierwsze dwa elementy (o indeksie zero i jeden) to 0 oraz 1, a każdy kolejny element jest sumą dwóch poprzednich. Elementem ciągu o indeksie dwa jest zatem 0 + 1 = 1, o indeksie trzy zaś  1 + 1 = 2 i tak dalej.

Definicja ciągu Fibonacciego ma następującą postać:

F n = { 0 dla  n = 0 1 dla  n = 1 F n 1 + F n 2 dla  n > 1

Rekurencja

Problem 1

Napisz w języku C++ program, który w sposób rekurencyjny znajdzie i wypisze element ciągu Fibonacciego o indeksie n. Przetestuj swój program dla elementu ciągu o indeksie 3.

Specyfikacja problemu:

Dane:

  • n – indeks elementu ciągu Fibonacciego, liczba naturalna

Wynik:

Program wypisuje element ciągu Fibonacciego o indeksie n.

RlIY5GYbhUFti
Wybierz jedno nowe słowo poznane podczas dzisiejszej lekcji i ułóż z nim zdanie.
Polecenie 1

Porównaj swoje rozwiązanie z prezentacją.

RxAtU9ca98bEL1
Wybierz dowolne angielskie słówko ze słowniczka i zapytaj kolegę o jego znaczenie.
Źródło: Contentplus.pl sp. z o.o., licencja: CC BY-SA 3.0.

Iteracja

Napiszemy program w języku C++, który w sposób iteracyjny znajdzie i wypisze element ciągu Fibonacciego o indeksie n. Implementację algorytmu zaczniemy od stworzenia funkcji iteracyjnej.

Linia 1. long long iter podkreślnik fibonacci otwórz nawias okrągły int n zamknij nawias okrągły otwórz nawias klamrowy. Linia 3. zamknij nawias klamrowy.

Ta funkcja również będzie zwracać wartość. Jej typ to long long, ponieważ wartości kolejnych wyrazów ciągu Fibonacciego rosną wykładniczo.

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.

Linia 1. long long iter podkreślnik fibonacci otwórz nawias okrągły int n zamknij nawias okrągły otwórz nawias klamrowy. Linia 2. if otwórz nawias okrągły n znak równości znak równości 0 zamknij nawias okrągły otwórz nawias klamrowy. Linia 3. return 0 średnik. Linia 4. zamknij nawias klamrowy else if otwórz nawias okrągły n znak równości znak równości 1 zamknij nawias okrągły otwórz nawias klamrowy. Linia 5. return 1 średnik. Linia 6. zamknij nawias klamrowy. Linia 7. zamknij nawias klamrowy.

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 (indeks kolejnego elementu ciągu Fibonacciego). Przypisujemy im odpowiednio wartości 0, 1 i 2.

Linia 1. long long iter podkreślnik fibonacci otwórz nawias okrągły int n zamknij nawias okrągły otwórz nawias klamrowy. Linia 2. if otwórz nawias okrągły n znak równości znak równości 0 zamknij nawias okrągły otwórz nawias klamrowy. Linia 3. return 0 średnik. Linia 4. zamknij nawias klamrowy else if otwórz nawias okrągły n znak równości znak równości 1 zamknij nawias okrągły otwórz nawias klamrowy. Linia 5. return 1 średnik. Linia 6. zamknij nawias klamrowy. Linia 8. long long a znak równości 0 średnik. Linia 9. long long b znak równości 1 średnik. Linia 10. int i znak równości 2 średnik. Linia 11. zamknij nawias klamrowy.

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.

Linia 1. long long iter podkreślnik fibonacci otwórz nawias okrągły int n zamknij nawias okrągły otwórz nawias klamrowy. Linia 2. if otwórz nawias okrągły n znak równości znak równości 0 zamknij nawias okrągły otwórz nawias klamrowy. Linia 3. return 0 średnik. Linia 4. zamknij nawias klamrowy else if otwórz nawias okrągły n znak równości znak równości 1 zamknij nawias okrągły otwórz nawias klamrowy. Linia 5. return 1 średnik. Linia 6. zamknij nawias klamrowy. Linia 8. long long a znak równości 0 średnik. Linia 9. long long b znak równości 1 średnik. Linia 10. int i znak równości 2 średnik. Linia 12. while otwórz nawias okrągły i otwórz nawias ostrokątny znak równości n zamknij nawias okrągły otwórz nawias klamrowy. Linia 14. zamknij nawias klamrowy. Linia 15. zamknij nawias klamrowy.

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 aktualizujemy wartości zmiennych a i b. Zmiennej a przypisujemy wartość zmiennej b, a zmiennej b wartość zmiennej suma. Na końcu zwiększamy wartość zmiennej i o 1.

Linia 1. long long iter podkreślnik fibonacci otwórz nawias okrągły int n zamknij nawias okrągły otwórz nawias klamrowy. Linia 2. if otwórz nawias okrągły n znak równości znak równości 0 zamknij nawias okrągły otwórz nawias klamrowy. Linia 3. return 0 średnik. Linia 4. zamknij nawias klamrowy else if otwórz nawias okrągły n znak równości znak równości 1 zamknij nawias okrągły otwórz nawias klamrowy. Linia 5. return 1 średnik. Linia 6. zamknij nawias klamrowy. Linia 8. long long a znak równości 0 średnik. Linia 9. long long b znak równości 1 średnik. Linia 10. int i znak równości 2 średnik. Linia 12. while otwórz nawias okrągły i otwórz nawias ostrokątny znak równości n zamknij nawias okrągły otwórz nawias klamrowy. Linia 13. long long suma znak równości a plus b średnik. Linia 14. a znak równości b średnik. Linia 15. b znak równości suma średnik. Linia 16. i znak równości i plus 1 średnik. Linia 17. zamknij nawias klamrowy. Linia 19. return b średnik. Linia 20. zamknij nawias klamrowy.

Po zakończeniu pętli wartość zmiennej jest zwracana jako wynik działania funkcji.

Oto cały kod programu:

Linia 1. kratka include otwórz nawias ostrokątny iostream zamknij nawias ostrokątny. Linia 2. using namespace std średnik. Linia 4. long long iter podkreślnik fibonacci otwórz nawias okrągły int n zamknij nawias okrągły otwórz nawias klamrowy. Linia 6. if otwórz nawias okrągły n znak równości znak równości 0 zamknij nawias okrągły otwórz nawias klamrowy. Linia 7. return 0 średnik. Linia 8. zamknij nawias klamrowy else if otwórz nawias okrągły n znak równości znak równości 1 zamknij nawias okrągły otwórz nawias klamrowy. Linia 9. return 1 średnik. Linia 10. zamknij nawias klamrowy. Linia 12. long long a znak równości 0 średnik. Linia 13. long long b znak równości 1 średnik. Linia 14. int i znak równości 2 średnik. Linia 16. while otwórz nawias okrągły i otwórz nawias ostrokątny znak równości n zamknij nawias okrągły otwórz nawias klamrowy. Linia 17. long long suma znak równości a plus b średnik. Linia 18. a znak równości b średnik. Linia 19. b znak równości suma średnik. Linia 20. i znak równości i plus 1 średnik. Linia 21. zamknij nawias klamrowy. Linia 23. return b średnik. Linia 24. zamknij nawias klamrowy. Linia 26. int main otwórz nawias okrągły zamknij nawias okrągły otwórz nawias klamrowy. Linia 27. cout otwórz nawias ostrokątny otwórz nawias ostrokątny iter podkreślnik fibonacci otwórz nawias okrągły 3 zamknij nawias okrągły otwórz nawias ostrokątny otwórz nawias ostrokątny endl średnik. Linia 28. return 0 średnik. Linia 29. zamknij nawias klamrowy.

Słownik

argument funkcji
argument funkcji

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

ciąg Fibonacciego
ciąg Fibonacciego

ciąg liczb naturalnych, określony rekurencyjnie, w którym pierwsze dwa elementy to 0 oraz 1, a każdy kolejny element jest sumą dwóch poprzednich

instrukcja warunkowa
instrukcja warunkowa

element języka programowania, który pozwala na wykonanie odpowiedniej instrukcji, w zależności od tego, czy wyrażenie logiczne zapisane jako warunek okaże się prawdziwe, czy fałszywe

nieskończona pętla
nieskończona pętla

pętla, która nigdy nie spełnia warunku zakończenia i nie zostanie zakończona

parametr funkcji
parametr funkcji

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)

rekurencja
rekurencja

wywoływanie funkcji przez siebie samą, aż do momentu zrealizowania wyznaczonego celu