12. Rekurencja. Ciągi zadane rekurencyjnie.
Treści kształcenia: Ciągi zadane rekurencyjnie w informatyce. Granice ciągów, algorytmy obliczania granic (problem zbieżności). Problem stopu w algorytmach. Interpretacja geometryczna (aplety) (na ćwiczeniach: obliczanie granic, punkty skupienia ciągów).
Efekty kształcenia: Student potrafi operować ciągami rekurencyjnymi, badać ich zbieżność i granice. Potrafi określić podstawowe wady i zalety takich ciągów w zastosowaniach informatycznych.
Wstęp - Rekurencja¶
Rekurencja - odwoływanie się np. funkcji lub definicji do samej siebie.
Dalej będziemy się jednak zajmować głównie ciągami zdefiniowanymi rekurencyjnie. O rekurencji już mówiliśmy przy okazji wprowadzenia pojęcia ciągu. Teraz poszerzymy i usystematyzujemy materiał. To naturalna metoda definiowania wielu ciągów.
Na początek: znany (być może) ciąg Fibonacciego
ma wzór analityczny (ogólny) - co wykażemy, czyli możemy też nie korzystać z rekurencji:
gdzie jest tzw. ,,złotą proporcją’’ ().
Wyprowadzimy ten wzór i posłuży nam do wprowadzenia ogólnej metody badania ciągów rekurencyjnych (tylko podstawowe zasady!). Zastanowimy się, która z metod definiowania ciągu jest lepsza (i kiedy).
A inne znane nam ciągi zadane rekurencyjnie? Wspomnijmy też o ciągu arytmetycznym oraz geometrycznym , których definicje są oczywiście rekurencyjne.
Nie zapominajmy też o rekurencyjnym definiowaniu silni: , czyli dla
mamy oraz .
Tak samo rekurencyjnie definiujemy też potęgi o wykładniku naturalnym...
I podstawowa informacja dla informatyków: jak się okaże, rekurencja to jedna z podstawowych metod obliczeniowych (i algorytmicznych), w trakcie zajęć postaramy się podać wiele ich zastosowań. W wielu problemach algorytmy wykorzystujące ciągi rekurencyjne są najprostszym, eleganckim, o krótkim kodzie i najbardziej naturalnym sposobem ich rozwiązywania, ale jednocześnie ma niekiedy wady (np. wzrost złożoności czasowej). Stąd warto poznać możliwości wprowadzania ciągów rekurencyjnych, ale i ich zamiany (o ile to możliwe) na wzory ogólne i procedury iteracyjne.
Rozpoczniemy od najprostszych pytań (zadania maturalne!):
Ćwiczenie 1¶
Ciąg dla każdego spełnia warunek
Wyraz tego ciągu jest równy:
Rozwiązanie.
Podstawiamy w danym wzorze rekurencyjnym kolejno , a potem . Czyli
Podstawiamy teraz z drugiego równania do pierwszego:
Odpowiedź: A).
Ćwiczenie 2¶
Ciąg jest określony wzorem
dla każdej liczby naturalnej . Wiadomo ponadto, że i . Wyraz jest równy:
Rozwiązanie.
Podstawiamy w danym wzorze rekurencyjnym oraz :
czyli
Odejmujemy teraz od pierwszego równania drugie i mamy , .
Odpowiedź: A).
Ćwiczenie 3¶
Ciąg jest określony wzorem
dla każdej liczby naturalnej . Oblicz sumę pięćdziesięciu początkowych wyrazów tego ciągu. Uwaga: - dowolne!
(samodzielnie)
Ćwiczenie 4¶
Pierwszymi wyrazami ciągu są: 2, 8, 14, ... Znajdź parametry i , dla których określa go następujący wzór rekurencyjny
Odpowiedź: .
Informatycy pewnie nie zwrócili na to uwagi - ale istotne jest pytanie: skąd wiemy, że rekurencja poprawnie nam coś definiuje? Na szczęście mamy
Twierdzenie. Niech będzie dowolnym zbiorem. Załóżmy, że i . Istnieje wtedy dokładnie jedna funkcja taka, że oraz dla .
Pytanie. Ważną kwestią jest, czy ciąg zdefiniowany rekurencyjnie możemy zapisać wzorem ogólnym (a wiemy, że tak jest dla ciągu arytmetycznego i geometrycznego)? A odwrotnie?
A co więcej: jeśli ciąg wyrazimy w obu postaciach, to która z postaci jest ``korzystniejsza’’ - choćby pod względem symulacji komputerowych?
Odpowiedzi nie są oczywiste, ale pokażemy, że w przypadku rekurencji liniowej znamy algorytm uzyskiwania wzoru ogólnego (choć nie zawsze będzie efektywny - np. dla rekurencji wyższych stopni!). Na drugie pytanie też nie ma łatwej odpowiedzi - to ważny problem w informatyce. Warto prześledzić to na przykładzie problemu wież Hanoi ...
A dlaczego warto rozważać obie postacie (rekurencyjną i wzór ogólny)? Prosty
Przykład¶
Obliczanie żądanego wyrazu w ciągu. Rozpatrzmy dla ciąg arytmetyczny ( i są ustalone)
Zadanie polega na obliczeniu wyrazu .
Ze wzoru rekurencyjnego wymaga to obliczenia wszystkich poprzednich. Za to wykorzystanie wzoru ogólnego
Tak dla przypomnienia: powyższy dowód formalnie wymaga zastosowania indukcji matematycznej - polecam to jako samodzielne ćwiczenie. Za to z tego wzoru mamy w jednym prostym kroku
Złożoność obliczeniowa jest znacznie mniejsza (1 krok zamiast 999 kroków obliczeniowych).
Są więc sytuacje, w których warto znać wzór ogólny (choć nie zawsze to będzie możliwe). Pokażemy, jak to zrobić w przypadku rekurencji liniowej... Ale rozpoczniemy od rozwiania wątpliwości, czy w ogóle potrzebujemy narzędzi matematycznych - może wystarczy komputer?
Rekurencja - pułapki komputera...¶
Czy komputer może być przydatny? Albo w ogóle zastąpić obliczenia matematyczne? Po kolei odpowiemy na te pytania. Czy może być przydatny - tak, ale...
Przykład (A)¶
Rozpatrzmy najpierw próbę wyznaczenia wartości przybliżonej liczby . Zastosujemy algorytm Archimedesa: geometryczną metodę opartą o wpisywanie kolejnych wielokątów foremnych w koło.
Liczymy , czyli wpisujemy kwadrat. Promień okręgu wynosi , czyli przekątna kwadratu ma długość 1, a więc bok . W tej konstrukcji dokonujemy w kolejnych krokach podwojenia liczby boków, czyli

w trójkącie o 2 bokach równych promieniowi (czyli: ) oraz trzecim boku będącym odcinkiem o końcach na okręgu (bok wielokąta ) o długości jego wysokość (z twierdzenia Pitagorasa) wyniesie
W takim razie w trójkącie równoramiennym o bokach (podstawa) i (ramiona) jego wysokość wyniesie
Ponownie stosując twierdzenie Pitagorasa uzyskamy
Czyli ostatecznie podstawiając i porządkując wyrazy uzyskujemy
Teraz obliczamy obwód tego wielokąta
Jeżeli ten ciąg jest zbieżny (o czym później), to do liczby .
O tym, jak badać istnienie granicy - już wkrótce (do tematu wrócimy), a teraz o sile i słabości obliczeń
na komputerze. Czy musimy użyć matematyki, a może wystarczy komputer? Wykonamy symulację w oparciu
o obliczenia na komputerze kolejnych wyrazów ciągu i spójrzmy, czy faktycznie dostaniemy poprawną hipotezę.
Obliczmy:
R = RealField(55)
a = sqrt(R(2))/2
m = 4
n = 1
nmax = 34
while n < nmax:
O = m*a
print("O[%s]=%s"%(n,O))
m = m*2
a=sqrt(2-sqrt(4-4*a*a))/2
n = n+1O[1]=2.828427124746190
O[2]=3.061467458920718
O[3]=3.121445152258052
O[4]=3.136548490545939
O[5]=3.140331156954757
O[6]=3.141277250932793
O[7]=3.141513801144290
O[8]=3.141572940366725
O[9]=3.141587725277645
O[10]=3.141591421513899
O[11]=3.141592345574021
O[12]=3.141592576693229
O[13]=3.141592634056147
O[14]=3.141592647692809
O[15]=3.141592645321216
O[16]=3.141592645321216
O[17]=3.141592607375720
O[18]=3.141592910939673
O[19]=3.141594125195191
O[20]=3.141596553704820
O[21]=3.141596553704820
O[22]=3.141674265021758
O[23]=3.141829681889201
O[24]=3.142451272494134
O[25]=3.142451272494134
O[26]=3.162277660168379
O[27]=3.162277660168379
O[28]=3.464101615137755
O[29]=4.000000000000000
O[30]=0.0000000000000000
O[31]=0.0000000000000000
O[32]=0.0000000000000000
O[33]=0.0000000000000000
I - niestety - efekt jest inny od oczekiwanego! Mogło się wydawać, że niezłe przybliżenie
jest osiągane (dla ), a tu zwiększanie liczby boków sugeruje wynik zero (??). Czy w związku z tym ciąg w ogóle jest zbieżny???
Dlaczego symulacja nie działa? No cóż - to nie jest najlepszy algorytm, obliczamy wartości
niewymierne i nawet wykorzystanie dobrej biblioteki systemowej z funkcją sqrt nie pomogło
(problem: zero maszynowe). Zastanowimy się jeszcze, jak obliczane są pierwiastki w typowych bibliotekach,
co pozwoli z nich nie korzystać pisząc jedynie własny algorytm!
Dla chętnych - spróbować poprawić ten program!
Należy zachować ostrożność we wnioskach symulacji na komputerze. Prezentacja: ciąg rekurencyjny w prezentacji CDF - wymaga CDF Player
Ale czy wiemy, że nasze symulacje granic innych ciągów będą poprawne i pewne? Nie możemy być tego pewni, tylko matematyka to rozstrzyga! A dla nas to cenna informacja: niestety (?) - bez matematyki się nie obejdzie...
Problem podzielimy na dwa: po pierwsze wykażemy, że granica istnieje i dopiero w drugim kroku możemy ją wyznaczyć - ale - jak widać - kontrolując ograniczenia komputera. Do tego przykładu wrócimy, gdy już będziemy wiedzieli, jak sprawdzić zbieżność tego ciągu.
Uwaga też na kolejne zjawisko: pozorną zbieżność ciągu. To sytuacja, gdy wykonując symulację i obliczając pewną liczbę elementów wydaje się (!), że mamy kandydata na granicę, a ciąg w ogóle nie jest zbieżny!
Sprawdźmy:
dla ustalonych i oraz odpowiednio dobierając , i .
R = RealField(55)
c = R(14/10)
d = R(3/10)
k = 1
a0 = R(136.462554878752)
a1 = R(5.39650717129623)
nmax = 60
for n in range(1,nmax+1):
a2=d*a0+k-c*a1*a1
print("a[%s] = %s"%(n,a0))
a0,a1 = a1, a2a[1] = 136.4625548787520
a[2] = 5.396507171296230
a[3] = 1.167560953833307
a[4] = 0.7104741381062694
a[5] = 0.6435853848650074
a[6] = 0.6332592347753053
a[7] = 0.6316514536600149
a[8] = 0.6314007879574576
a[9] = 0.6313616990513867
a[10] = 0.6313556033465619
a[11] = 0.6313546526874743
a[12] = 0.6313545045457987
a[13] = 0.6313544812318629
a[14] = 0.6313544780035630
a[15] = 0.6313544767163468
a[16] = 0.6313544780233880
a[17] = 0.6313544753266455
a[18] = 0.6313544804860390
a[19] = 0.6313544705562790
a[20] = 0.6313544896578526
a[21] = 0.6313544529113052
a[22] = 0.6313545236020490
a[23] = 0.6313543876115133
a[24] = 0.6313546492217822
a[25] = 0.6313541459519106
a[26] = 0.6313551141116017
a[27] = 0.6313532516247501
a[28] = 0.6313568345614775
a[29] = 0.6313499419209406
a[30] = 0.6313632015394381
a[31] = 0.6313376934149013
a[32] = 0.6313867640848043
a[33] = 0.6312923638183979
a[34] = 0.6314739611638526
a[35] = 0.6311246000663658
a[36] = 0.6317966232166535
a[37] = 0.6305036176687572
a[38] = 0.6329902503142495
a[39] = 0.6282037655105725
a[40] = 0.6374011156919475
a[41] = 0.6196688744536965
a[42] = 0.6536350151541888
a[43] = 0.5877664360862457
a[44] = 0.7124333678009192
a[45] = 0.4657441058472500
a[46] = 0.9100454093562396
a[47] = -0.01973247417233714
a[48] = 1.272468504055125
a[49] = -1.272766273588904
a[50] = -0.8861670308430030
a[51] = -0.4812386912510168
a[52] = 0.4099229416073111
a[53] = 0.6203768473463076
a[54] = 0.5841624766695118
a[55] = 0.7083689353957711
a[56] = 0.4727475749136202
a[57] = 0.8996243031973389
a[58] = 0.008770830809469499
a[59] = 1.269779592496878
a[60] = -1.254645049687312
Ale jeśli liczymy dalej (np. ), to teraz nie ma pomysłu, co jest granicą (przykład za: [1.])!
Pamiętajmy: najpierw zapewniamy sobie, że badany ciąg jest zbieżny, a dopiero później robimy symulacje! A tu granica nie istnieje, ale dowód tego jest trudny - zamiast tego polecam symulację np. dla .
A kto nie wierzy w słabości symulacji, to poproszę samodzielnie - w dowolnym programie - obliczyć wyrazy ciągu
i sformułować hipotezę o jego zbieżności lub nie. Proszę o ostrożność...
Uwaga: proszę nie spodziewać się, że ciągi rekurencyjne to łatwe obiekty do badań czy symulacji komputerowych. Są bardzo ważne w zastosowaniach ciągi jak np. równanie logistyczne (rekurencyjne)
Nawet nie wiadomo, czy przy dowolnych te ciągi są zbieżne! Wszyscy ufający symulacjom komputerowym: proszę eksperymentować, np. dla
i zobaczyć to na rysunku... (w ostatnim przypadku - uważać = to tzw. chaos)
Uwaga: Mówiliśmy o ciągach rekurencyjnych, a wcześniej również o arytmetyce komputerowej. No to teraz przykład, gdy błędy obliczeń w arytmetyce ... pomagają. Szczegóły wymagają - niestety - więcej informacji zarówno z algebry, jak i informatyki! Krótko wyjaśnimy, o co chodzi.
Metoda potęgowa służy do przybliżania unormowanego wektora własnego odpowiadającego największej wartości własnej macierzy , nieosobliwej i dodatnio określonej. Idea tej metody jest bardzo prosta: dla wektora startowego mnożymy macierz przez ten wektor
czyli to rekurencja...
Okazuje się, że podczas sukcesywnego wymnażania przez macierz najszybciej rośnie składowa w kierunku wektora odpowiadającego największej wartości własnej. Co więcej, jeżeli zostanie w każdym kroku wydzielone przez swoją długość (znormalizowane), to składowe w pozostałych kierunkach będą wygaszane (coraz mniejsze). Algorytm powinien znajdować największą wartość własną macierzy (zastosowanie np. w macierzy wyszukiwań algorytmu ‘‘GooglePageRank’’ to odpowiada najbardziej trafnej odpowiedzi na pytanie zadane w wyszukiwaniu).
To gdzie problem? Wektor początkowy musi mieć niezerowy rzut na prostą wyznaczaną przez poszukiwany wektor własny. W przeciwnym wypadku (teoretycznie) ciąg nie jest zbieżny.
Ale - i tu uwaga błędy zaokrągleń - w praktyce obliczeniowej, nawet jeśli ma zerową składową w kierunku wektora odpowiadającego największej wartości własnej, to po kilku iteracjach ta składowa będzie niezerowa dzięki błędom zaokrągleń. Powoduje to, że metoda potęgowa jest (praktycznie) zbieżna dla każdego .
Ćwiczenie 5. Korzystamy z rekurencji: bierzemy kwadrat o boku i w kolejnych krokach dodajemy kolejne takie same kwadraty z prawej i u góry, ale tak, by nadal tworzyło to kwadrat. Czyli w kroku drugim mamy dodatkowo 3 kwadraty (góra, prawo i ‘‘narożnik’’ prawo-góra). Procedurę powtarzamy.
... można zrobić rysunek na tablicy ...
Pytanie: ile takich kwadratów będzie w -tym kroku? Wyprowadzić wzór rekurencyjny.
Odp.: ,
Zadania dodatkowe¶
(1) Definiujemy rekurencyjnie ciąg za pomocą wzorów: oraz dla . Obliczyć i pokazać, że wszystkie wyrazy ciągu są nieparzyste.
(2) Definiujemy rekurencyjnie ciąg za pomocą wzorów: oraz dla . Obliczyć i pokazać, że wszystkie wyrazy ciągu są nieparzyste.
(3) Podaj definicję rekurencyjną ciągu i wyraz ogólny tego ciągu.
(4) Mamy alfabet złożony z 0 i 1. Ciąg oznacza liczbę słów w tym alfabecie o długości , ale niezawierających układu znaków ‘‘01’’ (w takiej ustalonej kolejności). Obliczyć 5 pierwszych wyrazów tego ciągu i znaleźć wzór ogólny Odp.: ,
(5) Dany jest ciąg rekurencyjny
Obliczyć 5 pierwszych liczb. Dla ambitnych: pokazać, że . Nieco później poszukamy wzoru ogólnego na ten ciąg (czyli: ).
(6) Zbadaj zbieżność ciągu
(7) Wieże Hanoi. Proszę samodzielnie zapoznać się z problemem wież Hanoi i przeanalizować wzór rekurencyjny i wzór jawny (np. M. Sysło ,,Algorytmy’') Odp.: oraz
(8) Oblicz sumę początkowych wyrazów ciągu określonego dla , w którym
dla .
Rozwiązanie:
Najpierw skorzystamy ze wzoru na sumę kolejnych wyrazów ciągu geometrycznego:
Stąd
(9) Kupujemy na raty telewizor. Każda kolejna rata ma być mniejsza od poprzedniej o stałą kwotę. Ile zapłacimy za telewizor, jeśli spłacamy go w 7 ratach, z których czwarta rata była równa 350 zł?
(10) Podobnie jak w ćwiczeniu 5. podać wzór rekurencyjny dla takiej konstrukcji w 3 wymiarach, czyli bierzemy kostkę, a w kolejnych krokach dodajemy kostki w prawo, w głąb i w górę plus uzupełniamy wszystko do kostki sześciennej. Podać wzór rekurencyjny na ilość kostek potrzebnych w -tym kroku.
(11) Ułożyć własne zadanie na wzór ćwiczenia 5 i zadania (10): dwu- i trójwymiarowe konstrukcje mile widziane...