Wstęp do programowaia Wykład 8 Podstawowe techiki programowaia w przykładach rekurecja Jausz Szwabiński Pla wykładu: Wprowadzeie Silia Rekurecja kotra iteracja Symbol Newtoa Cecha podzielości przez 3 dla liczby w zapisie dziesiętym Kowersja liczby całkowitej do łańcucha zaków w dowolej bazie Wielomiay Hermite'a Wieża Haoi Trójkąt Sierpińskiego Bibliografia: Problem solvig with algorithms ad data structures usig Pytho, http://iteractivepytho.org/ruestoe/static/pythods/idex.html (http://iteractivepytho.org/ruestoe/static/pythods/idex.html)
Wprowadzeie Rekurecja, zwaa rówież rekursją to odwoływaie się fukcji do samej siebie: opiera się a założeiu istieia pewego stau początkowego wymaga istieia zdaia (lub zdań) staowiącego podstawę wioskowaia jej istotą jest tożsamość dziedziy i przeciwdziedziy reguły wioskowaia może podlegać tej samej regule zastosowaej poowie wyik wioskowaia Silia Silia liczby aturalej to iloczy wszystkich liczb aturalych ie większych iż. Formalie defiiuje się ją w astępujący sposób: Wartość 0! określa się osobo:! = k, 1 Zwróćmy uwagę, że powyższa defiicja może zostać przepisaa w postaci rekurecyjej: k=1 0! = 1! ={ 1, ( 1)!, Implemetacja fukcji a podstawie tej defiicji jest bardzo prosta: = 0 1
I [1]: def fac(): if >=1: retur *fac(-1) else: retur 1 I [2]: fac(0) Out[2]: 1 I [3]: fac(1) Out[3]: 1 I [4]: fac(2) Out[4]: 2 I [5]: fac(5) Out[5]: 120 I [6]: fac(100) Out[6]: 9332621544394415268169923885626670049071596826438162146859296389521 7599993229915608941463976156518286253697920827223758251185210916864 000000000000000000000000
Warto wspomieć, że w bibliotece mathzajdziemy gotową implemetację sili: I [7]: import math I [8]: math.factorial(100) Out[8]: 9332621544394415268169923885626670049071596826438162146859296389521 7599993229915608941463976156518286253697920827223758251185210916864 000000000000000000000000 Rekurecja kotra iteracja Niewątpliwą zaletą rekurecji jest przejrzystość programów, które z iej korzystają. Rekurecja jest podstawową techiką wykorzystywaą w fukcyjych językach programowaia (p. Haskell, Lisp). Chociaż dla pewych problemów staowi oa aturaly wybór, powio stosować się ją z umiarem. Dla ilustracji rozważmy iteracyją wersję fukcji silia: I [9]: def fac_iter(): sil = 1 if >1: for i i rage(2,+1): sil = sil*i retur sil I [10]: fac_iter(0) Out[10]: 1 I [11]: fac_iter(2) Out[11]: 2 I [12]: fac_iter(5) Out[12]: 120
I [13]: fac_iter(100) Out[13]: 9332621544394415268169923885626670049071596826438162146859296389521 7599993229915608941463976156518286253697920827223758251185210916864 000000000000000000000000 Porówajmy teraz czasy wykoaia obu wersji fukcji silia: I [14]: %%timeit fac(120) 10000 loops, best of 3: 33.1 µs per loop I [15]: %%timeit fac_iter(120) 100000 loops, best of 3: 15 µs per loop Wprawdzie w tym kokretym przykładzie ie staowi to dla as jakiegoś większego problemu, ale ewidetie metoda rekurecyja jest dużo woliejsza od iteracyjej. Rekurecja potrafi dramatyczie zwiększyć złożoość obliczeiową wykoywaego programu, jeżeli rozwiązyway problem ie ma rekurecyjego charakteru. Ie wady: rekurecja zwiększa zapotrzebowaie programu a pamięć operacyją kompletie iezależe rozwiązywaie problemów (iektóre wartości wyliczae są wielokrotie)
Symbol Newtoa Mimo wspomiaych wad stosowaie rekurecji jest czasami kuszące ze względu a dużą przejrzystość kodu. Poiżej omówioych zostaie kilka przykładów, w których moża zastosować rekurecję. ( ) = Jedym z takich przykładów jest symbol Newtoa: k! k!( k)! Symbol te pojawia się we wzorze dwumieym Netwoa jako współczyik w tym wyrazie rozwiięcia tej potęgi sumy dwóch składików: x k y k (x + y ) = ( ) k=0 Stąd jego druga azwa: współczyik dwumiey Newtoa. Podaa powyżej defiicja jest rówoważa wzorowi rekurecyjemu: ( ) ={ k k 1, ( 1) + ( 1), k 1 k k {0, } 0 < k < k I [16]: def biom(,k): if k==0: retur 1 if ==k: retur 1 else: retur biom(-1,k-1) + biom(-1,k) I [17]: biom(7,2) #powio być 21 Out[17]: 21 I [18]: biom(9,3) #84 Out[18]: 84 Sprawdźmy wyik:
I [19]: fac(9)/(fac(3)*fac(9-3)) Out[19]: 84.0 Cecha podzielości przez 3 dla liczby w zapisie dziesiętym Cecha podzielości pozwala a stwierdzeie, czy daa liczba jest podziela bez reszty przez ią bez uciekaia się do dzieleia. W przypadku podzielości przez 3 cecha ma astępującą postać: liczba jest podziela przez 3, jeśli suma cyfr tej liczby jest podziela przez 3 Zauważmy, że regułę tę moża stosować rekurecyjie aż do osiągięcia liczby jedocyfrowej, której podzielość moża określić bardzo prosto, p.: 104628 1 + 0 + 4 + 6 + 2 + 8 = 21 2 + 1 = 3 Aby zaimplemetować sprawdzaie podzielości przez 3 metodą rekursywą, musimy ajpierw umieć rozbić dowolą liczbę a jej cyfry i zsumować je. W tym celu przekształcamy liczbę a łańcuch zaków: I [20]: umber = 2456 s = str(umber) prit(s) 2456 Następie z łańcucha tworzymy listę: I [21]: l = list(s) prit(l) ['2', '4', '5', '6'] Listę zaków kowertujemy a listę liczb całkowitych: I [22]: figs = [it(i) for i i l] prit(figs) [2, 4, 5, 6] I w ostatim kroku sumujemy elemety tej listy:
I [23]: sum(figs) Out[23]: 17 Korzystając z poleceia mapw Pythoie możemy powyższe kroki zapisać jedym poleceiem: I [24]: sum(map(it, str(umber))) Out[24]: 17 Możemy teraz zaimplemetować aszą fukcję: I [25]: def divisible_by_3(umber): ret = False if umber i (3,6,9): ret = True if umber > 9: ret = divisible_by_3(sum(map(it, str(umber)))) retur ret I [26]: divisible_by_3(3) Out[26]: True I [27]: divisible_by_3(4) Out[27]: False I [28]: divisible_by_3(10) Out[28]: False I [29]: divisible_by_3(12) Out[29]: True
I [30]: divisible_by_3(104628) Out[30]: True Kowersja liczby całkowitej do łańcucha zaków w dowolej reprezetacji Załóżmy teraz, że aszym zadaiem jest kowersja liczby całkowitej do łańcucha zaków w dowolej reprezetacji (od biarej do szesastkowej). Dla przykładu możemy chcieć zaprezetować liczbę 10 jako apis "10" w reprezetacji dziesiętej, lub jako "1010" w reprezetacji dwójkowej. Dla ustaleia uwagi załóżmy, że iteresuje as reprezetacja dziesięta. Jeśli zdefiiujemy łańcuch zaków odpowiadający wszystkim cyfrom w tej reprezetacji, I [31]: covstrig = "0123456789" to bardzo łatwo będzie am przekowertować dowolą liczbę miejszą od 10. Jeśli aszą liczbą będzie p. 9, to odpowiadający jej zak otrzymamy po prostu jako I [32]: covstrig[9] Out[32]: '9'
Aby przekowertować większą liczbę, p. 769, musimy ją zatem rozbić ajpierw a trzy cyfry a astępie każdą z cyfr zamieić a odpowiedi zak i połączyć zaki ze sobą. Wykorzystamy w tym celu dzieleie całkowite. Zauważmy, że dzieląc całkowicie 769 przez 10, otrzymamy 76 i resztę z dzieleia 9 dzieląc całkowicie 76 przez 10, otrzymamy 7 i resztę z dzieleia 6 dzieląc całkowicie 7 przez 10, otrzymamy 0 i resztę z dzieleia 7 Zauważmy, że reszty z dzieleia to są cyfry składające się a rozważaą liczbę. Każdą z ich możemy zamieić a zak jak w powyższym przykładzie. Rekurecyja wersja tego algorytmu będzie miała astępującą implemetację: I [33]: def tostr(,base): covertstrig = "0123456789ABCDEF" if < base: retur covertstrig[] else: retur tostr(//base,base) + covertstrig[%base] I [34]: prit(tostr(1453,10)) 1453 I [35]: prit(tostr(1453,2)) 10110101101 I [36]: prit(tostr(1453,8)) 2655
I [37]: prit(tostr(1453,16)) 5AD Wielomiay Hermite'a Wielomiay Hermite'a to przykład wielomiaów ortogoalych, używaych między iymi w mechaice kwatowej. Są oe rozwiązaiem rówaia rekurecyjego: (x) = 2x (x) 2 (x) H +1 H H 1 przy warukach początkowych: (x) = 1 H0 (x) = 2x H1 Kilka pierwszych wielomiaów powyższego ciągu ma postać: Poiżej "aiwa" implemetacja: H2(x) = 4x 2 2 H3(x) = 8x 3 12x (x) = 16 48 + 12 H4 x 4 x 2 I [38]: def hermite(,x): if(==0): f = 1e0 elif(==1): f = 2*x else: f = 2*x*hermite(-1,x)-2*(-1)*hermite(-2,x) retur f I [39]: x = 10 for i i rage(0,5): prit(hermite(i,x)) 1.0 20 398.0 7880.0 155212.0 I [40]: def h2(x): retur 4*x**2-2 def h3(x): retur 8*x**3-12*x def h4(x): retur 16*x**4-48*x**2 +12
I [41]: prit(h2(x)) prit(h3(x)) prit(h4(x)) 398 7880 155212
Wieża Haoi W prezetowaych do tej pory przykładach mieliśmy do czyieia z zagadieiami, które były zdefiiowae w sposób rekurecyjy. Dlatego zastosowaie rekurecji do ich implemetacji było bardzo aturale. Metoda ta sprawdza się jedak rówież w bardziej skomplikowaych problemach, które a pierwszy rzut oka ie zawsze wydają się rekurecyje. Przykładem takiego zagadieia może być wieża Haoi, zagadka wymyśloa w Azji i sprowadzoa do Europy przez fracuskiego matematyka Edouarda Lucasa w 1883 roku. Rozwiązaie zagadki polega a przeiesieiu wieży z jedego słupa a drugi krążek po krążku. Podczas przekładaia moża posługiwać się trzecim słupem (buforem), jedak przy założeiu, że ie wolo kłaść krążka o większej średicy a miejszy ai przekładać kilku krążków jedocześie. Jest to przykład zadaia, którego złożoość obliczeiowa wzrasta iezwykle szybko w miarę zwiększaia parametru wejściowego. Rozwiązaie dla 4 krążków zilustrowae jest a poiższym rysuku: Ogólie dla Dla = 64 krążków ajmiejsza liczba wymagaych ruchów wyosi L() = 2 1 daje to a przykład 2 64 1 = 18446744073709551615 584942417355 Zakładając, że ręczie moża wykoać 1 ruch a sekudę, przeiesieie wieży zajęłoby lat. Oczywiście komputery wykoują dużo więcej operacji w ciągu sekudy. Chcąc rozwiązać zagadkę a komputerze, zauważmy, że problem da się zapisać w postaci stosukowo prostego algorytmu rekurecyjego. Niech będzie liczbą krążków, atomiast koleje słupy ozaczoe są literami A, B i C. Wówczas: 1 A B C 1. przeieś (rekurecyjie) krążków ze słupka a słupek posługując się słupkiem, 2. przeieś jede krążek ze słupka A a słupek C, 3. przeieś (rekurecyjie) krążków ze słupka a słupek posługując się słupkiem. 1 B C A
Przykładowa implemetacja w Pythoie mogłaby wyglądać tak: I [42]: def movetower(,a, C, B): if >= 1: movetower(-1,a,b,c) movedisk(a,c) movetower(-1,b,c,a) I [43]: def movedisk(fp,tp): prit("movig disk from",fp,"to",tp) I [44]: movetower(3,"a","b","c") movig disk from A to B movig disk from A to C movig disk from B to C movig disk from A to B movig disk from C to A movig disk from C to B movig disk from A to B I [45]: movetower(4,"a","b","c") movig disk from A to C movig disk from A to B movig disk from C to B movig disk from A to C movig disk from B to A movig disk from B to C movig disk from A to C movig disk from A to B movig disk from C to B movig disk from C to A movig disk from B to A movig disk from C to B movig disk from A to C movig disk from A to B movig disk from C to B
Trójkąt Sierpińskiego Trójkąt Sierpińskiego to jede z ajprostszych fraktali (zaych długo przed powstaiem tego pojęcia). Kostrukcja tego zbioru podaa była w 1915 przez polskiego matematyka Wacława Sierpińskiego: 1. W trójkącie rówoboczym połącz środki boków, dzieląc go a cztery miejsze trójkąty. 2. Usuń środkowy z powstałych trójkątów. 3. Powtórz kroki 1 3 dla pozostałych trójkątów. Tym razem ie tylko będziemy chcieli zaimplemetować rekurecyją metodę tworzeia trójkąta Sierpińskiego, ale zilustrować cały proces a ekraie. W tym celu użyjemy prostego modułu turtle, który udostępia arzędzia do rysowaia i przesuwaia obiektu zwaego żółwiem a ekraie. Dokumetację do modułu moża zaleźć pod adresem https://docs.pytho.org/3.0/library/turtle.html (https://docs.pytho.org/3.0/library/turtle.html). Jego użycie jest dość proste: I [46]: import turtle w = turtle.scree() alex = turtle.turtle() alex.forward(50) alex.left(90) alex.forward(30) w.exitoclick() # Allows us to use turtles # Creates a playgroud for turtles # Create a turtle, assig to alex # Tell alex to move forward by 50 uits # Tell alex to tur by 90 degrees # Complete the secod side of a rectagle # Wait for user to close widow Wiele cech żółwia i plaszy, a której się porusza, możemy zmieiać, p.: I [47]: import turtle w = turtle.scree() w.bgcolor("lightgree") w.title("hello, Tess!") tess = turtle.turtle() tess.color("blue") tess.pesize(3) # Set the widow backgroud color # Set the widow title # Tell tess to chage her color # Tell tess to set her pe width tess.forward(50) tess.left(120) tess.forward(50) w.exitoclick() Możemy przejść teraz do implemetacji właściwego algorytmu:
I [48]: import turtle def drawtriagle(poits,color,myturtle): """ Draw triagle give by poits (helper fuctio)""" myturtle.fillcolor(color) myturtle.up() myturtle.goto(poits[0][0],poits[0][1]) myturtle.dow() myturtle.begi_fill() myturtle.goto(poits[1][0],poits[1][1]) myturtle.goto(poits[2][0],poits[2][1]) myturtle.goto(poits[0][0],poits[0][1]) myturtle.ed_fill() def getmid(p1,p2): """Fid midpoit of triagle's edge (helper fuctio)""" retur ( (p1[0]+p2[0]) / 2, (p1[1] + p2[1]) / 2) def sierpiski(poits,degree,myturtle): """Geerate Sierpiski Triagle with recursio""" colormap = ['blue','red','gree','white','yellow','violet','orage'] drawtriagle(poits,colormap[degree],myturtle) if degree > 0: sierpiski([poits[0], getmid(poits[0], poits[1]), getmid(poits[0], poits[2])], degree-1, myturtle) sierpiski([poits[1], getmid(poits[0], poits[1]), getmid(poits[1], poits[2])], degree-1, myturtle) sierpiski([poits[2], getmid(poits[2], poits[1]), getmid(poits[0], poits[2])], degree-1, myturtle) def mai(): myturtle = turtle.turtle() mywi = turtle.scree() mypoits = [[-100,-50],[0,100],[100,-50]] sierpiski(mypoits,4,myturtle) mywi.exitoclick() mai() I [ ]: