Скачать презентацию Sld 2 1 Rekurencja — to jeden z Скачать презентацию Sld 2 1 Rekurencja — to jeden z

Sld 2. RekB.ppt

  • Количество слайдов: 25

Sld 2. 1. Rekurencja - to jeden z najważniejszych mechanizmów używanych w informatyce, zwana Sld 2. 1. Rekurencja - to jeden z najważniejszych mechanizmów używanych w informatyce, zwana również Rekursją (Eng. – Recursion). Rozwiązanie problemu charakteryzuje się następującymi cechami, typowymi dla algorytmów rekurencyjnych: • „duży" problem jest rozłożony na problemy elementarny, który umiemy rozwiązać, i na problem o mniejszym stopniu skomplikowania niż ten, z którym mieliśmy do czynienia na początku, • zakończenie algorytmu jest jasno określone.

Sld 2. 2. Algorytm Euklidesa (365 -300 p. n. e. ) na obliczanie największego Sld 2. 2. Algorytm Euklidesa (365 -300 p. n. e. ) na obliczanie największego wspólnego dzielnika (NWD) dwóch liczb a i b Dane wejściowe: a i b; a > b dopóki c > 0 wykonuj: podstaw za c = resztę (a: b) ; podstaw za a liczbę b; podstaw za b liczbę c; dopóki c > 0 wykonuj: Rezultat: NWD = b. podstaw za c = resztę (a: b) ; podstaw za a liczbę b; podstaw za b liczbę c; a , b c = Resztę (a : b) Rezultat: NWD = b. TAK c > 0 ? NIE NWD = b a = b; b = c

Sld 2. 3. Przykład 2. Sprzątanie klocków Zadanie: Zebrania do pudełka wszystkich klocków, które Sld 2. 3. Przykład 2. Sprzątanie klocków Zadanie: Zebrania do pudełka wszystkich klocków, które rozsypane są na podłodze. Polecenie jest bardzo proste: „Zbierz to wszystko razem i poukładaj w pudełku". Procedura glówna: Wziąć jeden klocek z podłogi i włożyć do pudełka. Konec: Moment wyczerpania się klocków.

Sld. 2. 4. „Program rekurencyjny jest to program, który wywołuje sam siebie Sld. 2. 4. „Program rekurencyjny jest to program, który wywołuje sam siebie". . . Dane wejściowe Dekompozycja problemu Typowy program rozwązania cząstkowego problemu o malej ilości zmiennych Czy wszystke cząstkowe problemy są rozwązane TAK Rezultat Następujacy cząstkowy problem NIE

Sld 2. 5. Przykład rekurencji Sld 2. 5. Przykład rekurencji

 Sld. 2. 6. Błędy konstruowania programów rekurencyjnych Podstawowymi błędami popełnianymi przy konstruowaniu programów Sld. 2. 6. Błędy konstruowania programów rekurencyjnych Podstawowymi błędami popełnianymi przy konstruowaniu programów rekurencyjnych są: • złe określenie warunku zakończenia programu; • niewłaściwa (nieefektywna) dekompozycja problemu

Sld. 2. 7. Jak wykonują się programy rekurencyjne? Funkcja silnia Dane wejściowe N Stos Sld. 2. 7. Jak wykonują się programy rekurencyjne? Funkcja silnia Dane wejściowe N Stos dla N ! (LIFO: Last in - First out) Zapamiętaj 2 Zapamiętaj 3 0! = 1, n!= n* (n- 1)! , gdzie n ≥ 1. Zapamiętaj N Wylicz (N - 1) ! N ! = N (N - 1) ! Zapamiętaj N - 1 Wylicz (N - 2) ! (N - 1) ! = (N - 1) (N - 2) ! . . . . Zapamiętaj 3 Wylicz 2 ! 3 ! = 3 2 = 6 Zapamiętaj 2 Wylicz 1 ! 2 ! = 2 1 = 2 . . . . 1!=1 Zapamiętaj N -1 Zapamiętaj N . . . .

Sld. 2. 8. Funkcja silnia 4! Stos dla N = 4 (LIFO: Last in Sld. 2. 8. Funkcja silnia 4! Stos dla N = 4 (LIFO: Last in First out) Zapamiętaj 2 Zapamiętaj 3 Zapamiętaj 4 Wylicz 3 ! 4 ! = 4 6 = 24 Zapamiętaj 3 Wylicz 2 ! Dane wejściowe N = 4 3!=3 2=6 Zapamiętaj 2 Wylicz 1 ! 2!=2 1!=1

Sld. 2. 9. Program dla „Funkcja silnia” w C ++ Sld. 2. 9. Program dla „Funkcja silnia” w C ++

Sld. 2. 10. Niebezpieczeństwa rekurencji • Ciąg Fibonacciego fib(0) = 1, fib(1) = 1, Sld. 2. 10. Niebezpieczeństwa rekurencji • Ciąg Fibonacciego fib(0) = 1, fib(1) = 1, fib(n) = f(n - I) + fib(n - 2) gdzie n ≥ 2 • Nf = 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, . . . • Program unsigned long int fib(int x) { if (x<2) return 1; else return fib(x-l) + fib(x-2); } fib. cpp

Sld. 2. 11. Obliczanie Fib(4) fib(4) = 5 fib(3) = 3 fib(2) = 2 Sld. 2. 11. Obliczanie Fib(4) fib(4) = 5 fib(3) = 3 fib(2) = 2 fib(0) = 1 fib(1) = 1 fib(2) = 2 fib(1) = 1 Każde „zacieniowane" wyrażenie stanowi problem elementarny; problem o rozmiarze n>2 zostaje „rozbity" na dwa problemy o mniejszym stopniu skomplikowania: n-1 i n-2.

Sld. 2. 12. Obliczanie fib(5)=8 fib(4)=5 fib(3)=3 fib(2)=2 fib(0)=1 fib(2) = 2 fib(1)= 1 Sld. 2. 12. Obliczanie fib(5)=8 fib(4)=5 fib(3)=3 fib(2)=2 fib(0)=1 fib(2) = 2 fib(1)= 1 fib(1)=1 fib(2)=2 fib(1)=1 fib(0)=1 fib(1)=1 Znaczna część obliczeń jest wykonywana więcej niż jeden raz. Np. cała gałąź zaczynająca się od fib(3) jest zdublowana!). Funkcja fib nie ma żadnej możliwości, aby to „zauważyć“. Jest to tylko program, który wykonuje to, co mu każemy.

Sld. 2. 13. Typowe przyczyny „zawieszania Sld. 2. 13. Typowe przyczyny „zawieszania" programów • • • zachwianie równowagi systemu operacyjnego przez „nielegalne" użycie jego zasobów; „nieskończone" pętle; brak pamięci; nieprawidłowe lub niejasne określenie warunków zakończenia programu; błąd programowania (np. zbyt wolno wykonujący się algorytm).

Sld. 2. 14. Przepełnienie stosu („Stack overflow! Sld. 2. 14. Przepełnienie stosu („Stack overflow! ") • Programy rekurencyjne są zazwyczaj dość pamięciożerne: z każdym wywołaniem rekurencyjnym wiąże się konieczność zachowania pewnych informacji niezbędnych do odtworzenia stanu sprzed wywołania, a to zawsze kosztuje pamięci. • Spotyka się programy rekurencyjne, dla których określenie maksymalnego poziomu zagłębienia rekurencji podczas ich wykonywania jest dość łatwe. Analizując program obliczający 3! widzimy od razu, że wywoła sam siebie tylko 3 razy; w przypadku funkcji fib szybka „diagnoza" nie przynosi już tak kompletnej informacji. • Przybliżone szacunki nie zawsze należą do najprostszych. Dowodzi tego funkcja Mac. Carthy'ego

Sld. 2. 15. Funkcja Mac. Carthy'ego maccr. cpp unsigned long int Mac. Carthy(int x) Sld. 2. 15. Funkcja Mac. Carthy'ego maccr. cpp unsigned long int Mac. Carthy(int x) { if (x > 100) return (x-10); else return Mac. Carthy(x+ll)); } Ilość wywołań funkcji Mac-Carthy w zależności od parametru wywołania x

Sld. 2. 16. Nieskończona ilość wywołań rekurencyjnych W wielu funkcjach rekurencyjnych, pozornie dobrze skonstruowanych, Sld. 2. 16. Nieskończona ilość wywołań rekurencyjnych W wielu funkcjach rekurencyjnych, pozornie dobrze skonstruowanych, może z łatwością ukryć się błąd polegający na sprowokowaniu nieskończonej ilości wywołań rekurencyjnych. Należy zwracać uwagę na to, czy dla wartości parametrów wejściowych należących do dziedziny wartości, które mogą być użyte, rekurencja się kiedyś kończy. n TAK Czy n = 1 ? NIE TAK NIE Czy n jest parzyste? SDW = 1 n : = n (n-2) n : = n (n-1)

Sld. 2. 17. Nieskończony ciąg wywołań rekurencyjnych int N(int n, int p) { if Sld. 2. 17. Nieskończony ciąg wywołań rekurencyjnych int N(int n, int p) { if (n==0) return 1; else return N(n-1, N(n-p, p) ) Powyższa definicja jest poprawna w tym sensie, iż dla dowolnych wartości n > 0 i p>0 jej wynik jest określony i wynosi 1. Regułą w jest to, iż wszystkie parametry funkcji rekurencyjnej dla typowego kompilatora C++ są ewaluowane jako pierwsze, a następnie dokonywane jest wywołanie samej funkcji. Taki sposób pracy jest zwany wywołaniem przez wartość. Problem może zaistnieć wówczas, gdy w wywołaniu funkcji spróbujemy umieścić ją samą. Zapętlenie jest spowodowane próbą obliczenia parametru p, tymczasem to drugie wywołanie jest w ogóle niepotrzebne do zakończenia funkcji! Istnieje w niej bowiem warunek obejmujący przypadek elementarny: jeśli n=0, to zwróć 1. Niestety, kompilator o tym nie wie i usiłuje obliczyć ten drugi parametr, powodując zapętlenie programu

Sld. 2. 18. Zalety i wady programu rekurencyjnego • Programy rekurencyjne mają jedną olbrzymią Sld. 2. 18. Zalety i wady programu rekurencyjnego • Programy rekurencyjne mają jedną olbrzymią zaletę: są łatwe do zrozumienia i zazwyczaj zajmują mało miejsca jeśli rozpatrujemy liczbę linii kodu użytego na ich realizację. Z tym ostatnim jest ściśle związana łatwość odnajdywania ewentualnych błędów. • Wady programu rekurencyjnego: - są typowo zachłanne w dysponowaniu pamięcią komputera (pamięciochłonny) , - niekiedy „zawieszają" system operacyjny. . .

Sld. 2. 19. Typy programów rekurencyjnych • Czy istnieją jakieś techniki programowania pozwalające usunąć Sld. 2. 19. Typy programów rekurencyjnych • Czy istnieją jakieś techniki programowania pozwalające usunąć (lub zredukować) powyższe wady z programu rekurencyjnego? Odpowiedź jest pozytywna! • Pewna klasa problemów rekurencyjnych da się zrealizować na dwa sposoby, dające taki sam efekt końcowy, ale różniące się realizacją praktyczną. Podzielmy metody rekurencyjne na dwa podstawowe typy: - rekurencja „naturalna"; rekurencja „z parametrem dodatkowym.

Sld. 2. 20. Rekurencja „z parametrem dodatkowym Sld. 2. 20. Rekurencja „z parametrem dodatkowym"' int silnia 2(int x, int tmp=l) { if (x==0) return tmp; else return silnia 2(x-1, x*tmp); } x = n; tmp =1; int silnial(int x) { if (x==O) return 1; else return x*silnial(x-l) } x = x -1; tmp = x*tmp Kroki NIE x=0? TAK n! = tmp 1. silnia(5 -1, 5*(tmp=1) = 5) 2. silnia(4 -1, 4*(tmp=5) = 20) 3. silnia(3 -1, 3*(tmp=20) = 60) 4. silnia(2 -1, 2*(tmp=60) = 120) 5. silnia(1 -1, 1*(tmp=120) = 120)

Sld. 2. 21. Zalety rekurencji „z parametrem dodatkowym Sld. 2. 21. Zalety rekurencji „z parametrem dodatkowym" jest ukryta w sposobie wykonywania programu. „ Bez parametru dodatkowego" wynik cząstkowy z najgłębszego poziomu rekurencji musi być przekazany przez kolejne poziomy do góry, do swojego pierwszego egzemplarza. Z każdym „zamrożonym" poziomem, który czeka na nadejście wyniku cząstkowego, wiąże się pewna ilość pamięci, która służy do odtworzenia wartości zmiennych tego poziomu (tzw. kontekst). Odtwarzanie kontekstu już samo w sobie zajmuje cenny czas procesora, który mógłby być wykorzystany np. na inne obliczenia. Program rekurencyjny „z parametrem dodatkowym" robi to wszystko nieco wydajniej. Parametr dodatkowy służy do przekazywania elementów wyniku końcowego, dysponując nim nie ma potrzeby przekazywania wyniku obliczeń do góry, „piętro po piętrze". Po prostu w momencie, w którym program stwierdzi, że obliczenia zostały zakończone, procedura wywołująca zostanie o tym poinformowana wprost z ostatniego aktywnego poziomu rekurencji. Nie ma żadnej potrzeby zachowywania kontekstu poszczególnych poziomów pośrednich, liczy się tylko ostatni aktywny poziom, który dostarczy wynik.

Sld. 2. 22. Spirala narysowana rekurencyjnie • Istota rekurencji polega głównie na znalezieniu właściwej Sld. 2. 22. Spirala narysowana rekurencyjnie • Istota rekurencji polega głównie na znalezieniu właściwej dekompozycji problemu void draw (int side) { if (side <= 34) // Recursive termination condition { line(side); right. Turn(90); draw(side +3) // Recursive call } }

Sld. 2. 23. Krzywa Gilberta Sld. 2. 23. Krzywa Gilberta

Sld. 2. 24. Uwagi praktyczne na temat technik rekurencyjnych Techniki rekurencyjne niosą one ze Sld. 2. 24. Uwagi praktyczne na temat technik rekurencyjnych Techniki rekurencyjne niosą one ze sobą zarówno plusy, jak i minusy: • Zasadniczą zaletą jest czytelność i naturalność zapisu algorytmów w formie rekursywnej. Procedury rekurencyjne są zazwyczaj klarowne i krótkie, dzięki czemu dość łatwo jest wykryć w nich ewentualne błędy. • Dużą wadą wielu algorytmów rekurencyjnych jest pamięciożerność: wielokrotne wywołania rekurencyjne mogą łatwo zablokować całą dostępną pamięć! Problemem jest tu niemożność łatwego jej oszacowania przez konkretny algorytm rekurencyjny. • Metodą na ominięcie kłopotów z pamięcią jest stosowanie rekurencji „z parametrem dodatkowym". Nie wszystkie jednak problemy dadzą się rozwiązać w ten sposób, programy używające tej metody tracą odrobinę na czytelności. • Nie powinniśmy używać rozwiązań rekurencyjnych, gdy: - w miejsce algorytmu rekurencyjnego można podać czytelny i/lub szybki program iteracyjny; - algorytm rekurencyjny jest niestabilny (np. dla pewnych wartości parametrów wejściowych może się zapętlić lub dawać „dziwne" wyniki). • Rekurencja skrośna: podprogram A wywołuje podprogram B, który wywołuje z kolei podprogram A. . UNIKAJMY ICH, jeśli tylko nie jesteśmy całkowicie pewni poprawności programu.

Literatura 1. P. Wróblewski. Algorytmy. Struktury danych i techniki programowania. Helion. 2001. Literatura 1. P. Wróblewski. Algorytmy. Struktury danych i techniki programowania. Helion. 2001.