REKURSIO Rekursiivinen ohjelma Kutsuu itseään Rekursiivinen rakenne Rakenne sisältyy itseensä Rekursiivinen funktio On määritelty itsensä avulla Esim. Fibonacci-luvut: X(i) = X(i-1) + X(i-2), X(0) = X(1) = 1 24.9.2015 Tietorakenteet ja algoritmit 1
Rekursio ei voi jatkua loputtomiin Täytyy löytyä päätösehto jonka toteutuminen lopettaa rekursion Esim. Kertoma: Rekursio: N! = N(N-1)! Päätösehto: 0! = 1 24.9.2015 Tietorakenteet ja algoritmit 2
Rekursion neljä kultaista sääntöä: 1. Perustapaukset (ratkaistavissa ilman rekursiota) 2. Edistyminen (liikutaan perustapausta kohti) 3. Oletus (kaikki rekursiiviset kutsut toimivat) 4. Vältä turhaa työtä (jokainen tapaus ratkaistaan vain kerran) 24.9.2015 Tietorakenteet ja algoritmit 3
Esim. Kertoma rekursiivisena: int factorial(int n) /* n: askeltaja */ { if (n==0) return 1; else return factorial(n-1)*n; Esim. Kertoma ei-rekursiivisena: int factorial(int n) /* n: kiintoarvo */ { int i,fact; fact = 1; /* fact: kokooja */ for (i=2; i<=n; i++) /* i: askeltaja */ fact = fact*i; return fact; 24.9.2015 Tietorakenteet ja algoritmit 4
Esim. Fibonacci-luvut: int fibonacci(int n) { if (n<=1) return 1; else return fibonacci(n-1)+fibonacci(n-2); F5 F6 F4 {Rikkoo neljättä sääntöä F4 F3 F3 F2 Parempi toteuttaa eirekursiivisena Mutta: voitaisiin muuntaa lineaarisessa ajassa toimivaksi taulukoinnilla! F2 F3 F2 F2 F1 F2 F1 F1 F0 F1 F0 F1 F1 F0 F1 F0 F1 F0 24.9.2015 Tietorakenteet ja algoritmit 5
Rekursiohistoriapuu Puurakenne, jossa solmu jokaista rekursiokutsua kohti Solmut aktivoituvat ns. esijärjestyksessä (ks. puiden läpikäynti) Ensimmäistä kutsua vastaa puun juuri Pinokehys aktiivinen, kunnes kaikki alipuut on käyty läpi Pinokehys poistuu, kun rekursio palaa solmusta Esim. Fibonaccin luvut: int fibonacci(int n) { if (n<=1) return 1; else return fibonacci(n-1)+fibonacci(n-2); 24.9.2015 Tietorakenteet ja algoritmit 6
Dynaaminen ohjelmointi Dynamic Programming Jaetaan ongelma (rekursiivisesti) osaongelmiin, joita yhdistelemällä alkuperäinen ongelma ratkeaa Mitä jos sama pienempi osaongelma joudutaan ratkaisemaan useaan kertaan? Tämä rikkoo rekursion 4. kultaista sääntöä! Vrt. Hajoita ja hallitse (esim. Quicksort), jossa sama aliongelma esiintyy vain kerran Ratkaisuja Top-down: memoization / taulukointi tai Bottom-up: koostaminen 24.9.2015 Tietorakenteet ja algoritmit 7
Taulukointi Vrt. Fibonaccin luvut: Taulukoimalla: var arr = new Array[Int](MAX_N); def fib_r(n: Int): Int = { if (n <= 1) 1 else { fib_r(n - 1) + fib_r(n - 2) def fib_m(n: Int): Int = { if (n <= 1) 1 else { if (arr(n) == 0) arr(n) = fib_m(n - 1) + fib_m(n - 2) arr(n) Esim. Fibonaccin luvut voidaan laskea pitämällä kirjaa jo lasketuista tapauksista Yleisesti pidetään kirjaa (top-down) jo laskettujen aliongelmien ratkaisuista ja tehdään laskentaa vain, jos ratkaisua ei vielä tunneta 24.9.2015 Tietorakenteet ja algoritmit 8
Koostaminen Esim. Fibonaccin luvut voidaan laskea myös lähtemällä liikkeelle tapauksista old = older = 1 ja laskemalla (bottom-up) silmukassa current = old + older ja päivittämällä old & older def fib_iter(n: Int) = { var old, older, current = 1 for (i <- 1 until n) { older = old old = current current = old + older current Yleisesti: koostetaan tunnetuista aliongelmien ratkaisuista monimutkaisempia ratkaisuja 24.9.2015 Tietorakenteet ja algoritmit 9
Esim. Hanoin tornit (Edouard Lucas, 1883): Siirrä renkaat toiseen tappiin yksi kerrallaan niin, että isompi rengas ei koskaan ole pienemmän päällä: Mistä Minne Vi a N K2.17? 24.9.2015 Tietorakenteet ja algoritmit 10
Esim. Hanoin tornit (Edouard Lucas, 1883): Ratkaisu rekursion avulla: Mistä Minne Vi a N-1 N-1 1 K2.17 24.9.2015 Tietorakenteet ja algoritmit 11
void hanoi(int N, int mista, int minne, int via) { if (N==1) printf( Siirrä rengas tapista %d tappiin %d kiitos\n, mista, minne); else { hanoi(n-1, mista, via, minne); hanoi(1, mista, minne, via); hanoi(n-1, via, minne, mista); Mistä Minne Vi a Kotitehtävä: Ohjelmoi hanoi haluamallasi ohjelmointikielellä ja testaa, että se toimii! K2.17 24.9.2015 Tietorakenteet ja algoritmit 12
Hanoin tornien rekursiohistoriapuu Esim. hanoin(3, 1, 2, 3) void hanoi(int N, int mista, int minne, int via) { if (N==1) printf( Siirrä rengas tapista %d tappiin %d kiitos\n, mista, minne); else { hanoi(n-1, mista, via, minne); hanoi(1, mista, minne, via); // printf hanoi(n-1, via, minne, mista); h(3,1,2,3) Mistä Minne Vi a K2.17 h(2,1,3,2) h(1,1,2,3) 24.9.2015 Tietorakenteet ja algoritmit 13
Rekursiohistoriapuu Esim. hanoi(3, 1, 2, 3) void hanoi(int N, int mista, int minne, int via) { if (N==1) printf( Siirrä rengas tapista %d tappiin %d kiitos\n, mista, minne); else { hanoi(n-1, mista, via, minne); hanoi(1, mista, minne, via); // printf hanoi(n-1, via, minne, mista); h(3,1,2,3) h(2,1,3,2) h(1,1,2,3) h(2,3,2,1) h(1,1,2,3) h(1,1,3,2) h(1,2,3,1) h(1,3,1,2) h(1,3,2,1) h(1,1,2,3) 24.9.2015 Tietorakenteet ja algoritmit 14
Analyysiä Mikä on Hanoin-tornit algoritmin aikakompleksisuus? Askelten lukumäärä s = 2 N -1 Todistus? void hanoi(int N, int mista, int minne, int via) { if (N==1) printf( Siirrä rengas tapista %d tappiin %d kiitos\n, mista, minne); else { hanoi(n-1, mista, via, minne); hanoi(1, mista, minne, via); hanoi(n-1, via, minne, mista); Perustapaukset s 1 = 1, s 2 = 3, s 3 = 7, Induktio-oletus: s N-1 = 2 N-1-1 (1) Induktioaskel: S N = S N-1 + 1 + S N-1 = 2S N-1 + 1 sij. (1) = 2(2 N-1-1) + 1 = 2 N - 1 24.9.2015 15
Rekursioyhtälöt Edellä olisi yhtä hyvin voitu merkitä vrt. s 1 = 1, s N = 2 N - 1 S(1) = 1, S(2) = 3, S(3) = 7 ja S(N) = 2 N 1 tai T(1) = 1, T(N) = 2 N 1 Algoritmianalyysissä useimmiten algoritmin aikakompleksisuuha merkitään T(N):llä Lisäksi haetaan suuruusluokkaa, eli esim. T(N) = O(2 N ) 24.9.2015 Tietorakenteet ja algoritmit 16
5.9 Rekursioyhtälöt Mentaalisen mallin muodostuminen (muna-kana-ongelma) 1. Rekursiohistoriapuut, laskennan etenemisen sisäistäminen 2. Laskennan vaativuuden erottaminen itse laskennen lopputuloksesta 3. Valikoitu arvaus laskennan vaativuudelle 4. Yhteys induktiotodistukseen 5. Rekursioyhtälö mentaalisen mallin ulkoistamisen välineenä Rekursioyhtälöiden ratkaisumenetelmiä (todistus) Aritmeettiset ja geometriset sarjakehitelmät Parametrien muunnokset Lausekkeen manipulointi Näiden yhdistely Master Theorem (Cormen) Induktiotodistus (e-book) 22.09.2015 Tietorakenteet ja algoritmit Y - syksy 2015 17
Esim 1. Linkitetyn listan läpikäynti silmukassa, poista joka kierroksella yksi alkio (ks. aritmeettinen sarja): T(N) = T(N-1) + N, T(1) =1 N T(N) = k = N (N + 1) / 2 k=1 T(N) on noin N 2 / 2 = O(N 2 ) T(N-1) = T((N-1)-1) + (N-1) = T(N-2) + N - 1 T(N-2) = T((N-2)-1) + (N-2) = T(N-3) + N - 2 T(N-k) = T((N-k)-1) + (N-k) = T(N-k-1) + N - k 22.09.2015 Tietorakenteet ja algoritmit Y - syksy 2015 18
Esim 2. Binäärihaku, puolita aineisto vakioajassa: T(N) = T(N/2) + 1, T(1) =1 N = 2 k, k = log 2 N (parametrin muunnos) T(2 k ) = T(2 k-1 ) + 1 = T(2 k-2 ) + 1 + 1 = T(2 0 ) + k = k +1 T(N) = O(k) = O(log N) Esim 3. Quick select, puolita aineisto lineaarisessa ajassa (geometrinen sarja): T(N) = T(N/2) + N, T(1) = 1 T(N) = N + N/2 + N/4 + N/8 +... = 2N = O(N) 22.09.2015 Tietorakenteet ja algoritmit Y - syksy 2015 19
Esim 4. Hajoita ja hallitse : Jaa aineisto kahteen osaan lineaarisessa ajassa, käsittele molemmat puolet rekursiivisesti T(N) = 2T(N/2) + N, T(1) = 1 N = 2 k, k = log 2 N T(2 k ) = 2T(2 k-1 ) + 2 k <=> T(2 k ) / 2 k = T(2 k-1 ) / 2 k-1 + 1 <=> T(2 k-1 ) / 2 k-1 = T(2 k-2 ) / 2 k-2 + 1 <=> T(2 k-2 ) / 2 k-2 = T(2 k-3 ) / 2 k-3 + 1... <=> T(2 2 ) / 2 2 = T(2 1 ) / 2 1 + 1 <=> T(2 1 ) / 2 1 = T(2 0 ) / 2 0 + 1 Lasketaan yhteen (teleskooppi) T(2 k ) / 2 k = k T(N) = T(2 k ) = k 2 k = N log N = O(N log N) 22.09.2015 Tietorakenteet ja algoritmit Y - syksy 2015 20
Esim 5. Potenssiinkorotus: Jos laskettaisiin x n = x*x*x*...*x, n-1 kertolaskua => O(n) Mutta voidaan laskea esim. x 9 = (x 2 ) 4 x = ((x 2 ) 2 ) 2 x (vain 4 kertolaskua 8:n sijaan!) integer pow(int x, int n){ switch (n) { case 0: return 1; break; case 1: return x; break; default: if (n&1) return pow(x*x, n/2)*x; else return pow(x*x, n/2); T(n) = T(n/2) + 1, T(1) = T(0) = 1 T(n) = O(log n) (Ratkaisu: vrt. Esim. kohta 2 edellä) 22.09.2015 Tietorakenteet ja algoritmit Y - syksy 2015 21
5.10 Kuinka analyysi tarkistetaan? Empiirisesti Toteuttamalla ohjelma Tehdään koeajoja mahdollisimman suurilla aineistoilla N, 2N, 4N, 8N,... Kun N tuplaantuu O(N) - algoritmien ajoaika tuplaantuu O(N 2 ) : nelinkertaistuu O(N 3 ) : kahdeksankertaistuu jne. O(log N) : vain vakiotermi lisää riippumatta N:stä O(N log N) : hiukan huonompi kuin O(N) Alemman asteen termit sotkevat empiiristä tarkastelua 22.09.2015 Tietorakenteet ja algoritmit Y - syksy 2015 22
Jos T(N) on mitattu suoritusaika ja oletetaan, että T(N) = O( f(n) ), niin voidaan tilastoida suuretta c = T(N)/f(N) satunnaisilla N:n arvoilla Jos c lähestyy positiivista arvoa f(n) on tiukka raja, ehkä T(N) = θ( f(n) ) Jos c lähestyy nollaa f(n) on liian lepsu raja, voidaan sanoa että T(N) = o( f(n) ) Jos arvot hajaantuvat f(n) kasvaa liian hitaasti, T(N) ei ole O( f(n) ) 22.09.2015 Tietorakenteet ja algoritmit Y - syksy 2015 23
5.11 Analyyttisten tulosten tulkinta Usein ei ole olemassa selvästi parasta algoritmia tai tietorakennetta. Valintaan vaikuttaa monia tekijöitä : kuinka usein ohjelmaa tarvitaan? syötteen koko (aineiston määrä) suoritusaikavaatimukset reaaliaikainen / eräluonteinen työ lisäysten / poistojen / päivitysten määrä ja suhde hakuihin Matemaattinen analyysi kertoo vain osan totuudesta. Muita tekijöitä ovat : kääntäjä käyttöjärjestelmä I/O:n määrä Yleensä kannattaa aloittaa yksinkertaisella menetelmällä (brute force well tuned) 22.09.2015 Tietorakenteet ja algoritmit Y - syksy 2015 24
Ongelman kasvaessa algoritmia voidaan vaihtaa tehokkaampaan uusimatta koko ohjelmaa N 2 N * LOG N Algoritmin viilaaminen katkaistaan sopivasti. Ohjelmakoodin optimointi käsin kannattaa harvoin. Tärkeämpää on se, että ohjelmassa on varauduttu muutoksiin hyvin ehjä kokonaisuus abstraktit tietotyypit Ohjelman ylläpidettävyys on usein tärkeämpää kuin tehokkuus! 22.09.2015 Tietorakenteet ja algoritmit Y - syksy 2015 25
Ensi kerraksi Tee viikon 39 viikkoharjoitukset loppuun Tutustu järjestämismenetelmiin ja kertaa asioita Ks. luentotehtävä (viikko 40) A+:ssa Voit tutustua myös lisää algoritmien suunnittelumenetelmiin (mm. taulukointi, dynaaminen ohjelmointi) Tee itseopiskeluna viikon 41 harjoitukset (prioritettijonot) ml. viikon 41 luentotehtävä 22.09.2015 Tietorakenteet ja algoritmit Y - syksy 2015 26