PG4200 Algoritmer og datastrukturer Forelesning 3 Rekursjon Estimering Lars Sydnes, NITH 22.januar 2014
I. Rekursjon
commons.wikimedia.org
Rekursjon i naturen En gren er et tre som sitter fast på et tre. Følgelig er et tre sammensatt av trær. Hva består blomkål av? Små blomkål som sitter fast på en sentral stamme. Vi kan lese selvrefererende struktur inn i disse fenomenene. REKURSJON
Rekursjon i matematikken Potenser: 2 n = 2 2 n 1 i.e. pow(2, n) = 2 pow(2, n 1) Summer: i.e. n i = n + i=1 n 1 i=1 sumints(n) = n + sumints(n 1) i
Algoritmisk rekursjon: Hvordan lage blomkål? public Blomk(double size) { Stamme stamme = new Stamme(); while ( stamme.hasnextfeste()) { stamme.nextfeste().add(new Blomk(size/3)); OBS: Hva er galt her?!
Rekursiv Noe som refererer til seg selv Rekursiv funksjon = Funksjon som kaller på seg selv. funksjon() { if stoppbetingelse { /*direkte strategi*/ else { /*rekursiv strategi*/ funksjon() Mønster: (i) Stoppbetingelse basis-tilfellet (base-case) (ii) Ellers rekursiv løsning.
Indirekte rekursjon public... funksjon1() { if (stoppbetingelse()) { return enkeltsvar(); else { return funksjon2(); private... funksjon2() { return bearbeid(funksjon1()); Dette kalles indirekte rekursjon. Det er ingen ting som forhindrer oss fra å ha mange lag av indirekte rekursjon.
Eksempel Backtracking SubsetSum.java Problem: Se på en gitt liste med tall, la oss si n 0, n 1,..., n k = 1, 43, 2, 5, 2, 3, 67, 5, 3, 6, 8 Velge en delmengde med gitt sum S, la oss si S = 13. Her har vi løsningen n 0 + n 2 + n 3 + n 7 = 1 + 2 + 5 + 5 = 13 = S
Rekursiv løsning: (i) Prøv først å bygge en løsning med det første elementet n 0 (ii) Prøv å finne en delmengde av n 1,..., n k med sum S n 0 Det lykkes: Vi har en løsning! Det mislykkes: Prøv å finne en delmengde av n 1,..., n k med sum S. Dette kalles Backtracking TEGNE FIGUR PÅ TAVLEN: VALGTRE. GJØRE OVERSLAG OVER KJØRETID.
II. Rekursjon, stakker, iterasjon
Eksempel int fak(int n) { // rekursiv variant if (n == 0) {//stoppbetingelse return 1; else { // Rekursiv strategi return n*fak(n-1); fak(n) = 1 2 3 (n 1) n = fak(n 1) n
Organisering av rekursive kall public static void main(string[] args){ int k = fak(4); return 24 return 6 return 2 return 1 main fak fak fak fak fak(4) fak(3) fak(2) fak(1) Argumenter og lokale variabler lagres i programmets funksjonskallstakk. (Call stack) Rekursive funksjonskall fører med seg implisitt strukturering og ressursbruk.
Hva er en stakk En stakk er en sist inn, først ut -kø. Papirer i en bunke. Funksjonskallstakken. De første skal bli de siste
Iterasjon rekursjon public static int fak(int n){ if (n <= 1){ return 1; else { return n*fak(n-1); public static int fak(n){ int output = 1; while (n > 1){ output *= n; n--; return output;
Oversikt Rekursivt kall Stoppbetingelse Rekursjon vs iterasjon Rekursjon kan iblant gi nærmest magiske løsninger. Det kan være krevende å estimere ressursbruken. Problemer som er av rekursiv natur: Web-crawling. Løsningsstrategier som kaller på rekursjon: Backtracking.
III. Mer om kjøretidsberegninger
Eksempel: Fakultet int fak(int n) { if (n <=1) { return 1; else { return n*fak(n-1); Hvordan estimere ressursbruken? Hva er det lurt å telle? Antall funksjonskall? Antall multiplikasjoner?
Eksempel: Fakultet Antall funksjonskall i beregning av fak(n): F n (Name and conquer) int fak(int n) { // F_n kall if (n <=1) { return 1; // ingen kall else { return n*fak(n-1); // F_(n-1) kall (i) F 1 = 1. Vi teller her kun det opprinnelige kallet fak(1). (ii) F n+1 = F n + 1. Følgelig er F n = n Konklusjon: Kall av fak(n) medfører n funksjonskall.
Eksempel: TowersOfHanoi.java Problem: Flytt tårnet fra venstre til høyre pinne. Se Applet Regeler: Én brikke om gangen. Store brikker får ikke ligge oppå små. Løsning for to brikker: 0 top bottom 1 bottom top 2 top bottom 3 top bottom
Eksempel: TowersOfHanoi.java 0 1 top bottom bottom top 2 top bottom 3 top bottom Rekursiv tankegang: (i) Flytt de n 1 øverste brikkene til midten. (ii) Flytt den nederste brikken. (iii) Flytt de n 1 øverste brikkene til høyre.
Eksempel: TowersOfHanoi.java M n = Antall trekk i tilfellet med n brikker. (Name and conquer) Rekursivt steg:... movetower(n-1,...); // M_(n-1) trekk moveonedisk(...); // 1 trekk movetower(n-1...); // M_(n-1) trekk... M n = 2M n 1 + 1 Basis-steg: if (n<1) { /* do nothing*/ M 0 = 0
Regning: Induksjonsbevis Innledende undersøkelse: M n = 2M n 1 + 1, M 0 = 0; M 1 = 2 M 0 +1 = 2 0+1 = 1 M 2 = 2 1+1 = 3, M 3 = 2 3+1 = 7 M 4 = 2 7 + 1 = 15, M 5 = 2 15 + 1 = 31 Hypotese: H n : M n = 2 n 1. (H n er sann for n = 1, 2, 3, 4, 5.) Kan H n være sann for alle n? Anta at H n er sann for alle n < k. Da er M k = 2M k 1 + 1 = 2 (2 k 1 1) + 1 = 2 2 k 1 2 + 1 = 2 k 1. Dette medfører at H n stemmer for n = k! Hurra! Vi vet fra før at hypotesen stemmer for n = 1, 2, 3, 4, 5. Men dette må bety at den stemmer også for n = 6. På tilsvarende måte ser vi at H n er sann for alle n.
Regning: Et lurt knep M n = 2M n 1 + 1, M 0 = 0; La U n = M n + 1. Da er U n = M n +1 = 2M n 1 +1+1 = 2(M n 1 +1) = 2U n 1, U 0 = 1 Dette medfører at U 0 = 1, U 1 = 2, U 2 = 2 2,..., U k = 2 k,..., Altså: M n = U n 1 = 2 n 1.
Eksempel: TowersOfHanoi.java Lukket formel: M n = 2 n 1 Data fra kjøring: Antall brikker: 0 1 2 3 4 5 6 7 Antall trekk: 0 1 3 7 15 31 63 127
Aritmetiske rekker S n = A + (A + B) + (A + 2B) + + (A + nb) Knep: Paring av ledd med totalsum (2A + nb) 2S n =S n + S n =(A + (A + B) + (A + 2B) + + (A + nb)) Dette betyr: + (A + (A + B) + + (A + (n 1)B) + (A + nb)) =(A + (A + nb) + (A + B) + (A + (n 1)B) + + (A + nb) + A) =((2A + nb) + (2A + nb) + + (2A + nb)) =(n + 1)(2A + nb) S n = 1 (n + 1)(2A + nb) 2
Aritmetiske rekker Se på summen Her er A = 0 og B = 1. Vi får S n = 1 + 2 + + n S n = 1 n(n + 1) 2 for(int i = 0 ; i < n; i ++){ function(i); // O(i) Dette betyr at det finnes en konstant K slik at kjøretiden til funksjon(i) er mindre enn K i. Total kjøretid blir da: T n = K 0 + K 1 + K 2 + + K n = 1 Kn(n + 1). 2
Geometriske rekker Knep: Det vil si: altså: S n = A + AB + AB 2 + AB n På de ene side: S n+1 = S n + AB n+1 På den annen side: S n+1 = A + BS n S n + AB n+1 = A + BS n, S n = A(1 Bn+1 ) 1 B Spesialtilfelle: A = 1: 1 + B + B 2 + B n = 1 Bn+1 1 B
Utnyttelse: Skarpere estimater for(int i = 1; i < n; i*=3){ function(i); // O(i) Her går i gjenom verdiene 1, 3,..., 3 k, der n 3 k < 3n. Anta at kjøretiden for funksjon(i) er mindre enn K i. Da er total kjøretid lik t(n) = K + 3 K + + 3 k K = K(1 3k+1 ) 1 3 = 3K 2 3k K 2. Siden 3 k < 3n, medfører dette at t(n) < 9K 2 n K 2, altså at t(n) er av orden O(n).
Utnyttelse: Skarpere estimater Dette gir estimatet for(int i = 1; i < n; i*=3){ // O(log(n)) function(i); // O(i) // O(n) Tommelfingerregelestimatet: for(int i = 1; i < n; i*=3){ // O(log(n)) function(i); // O(n) // O(n*log(n)) Dette estimatet gir en gyldig øvre grense for worst case-kjøretid: Kjøretiden er ikke værre enn O(n log n). MEN, det finnes et skarpere estimat: O(n).
Utnyttelse: Skarpere estimater? for(int i = 1; i < n; i*=3){ function(i); // O(log(i)) Her går i gjenom verdiene 1, 3,..., 3 k, der n 3 k < 3n. Anta at kjøretiden for funksjon(i) er mindre enn M log i. Da er total kjøretid lik t(n) = M + M log(3) + M log(3 2 ) + + M log(3 k ) = M + M log 3 + 2M log 3 + + km log(3) = M + M log 3(1 + 2 + + k) = M + M log 3 1 k(k + 1) 2 3 k < 3n medfører at k < log n log 3 + 1 < M log n for en eller annen M. t(n) < M log 3 1 2 M log n(m log n + 1) < M log(n) 2
Utnyttelse: Skarpere estimater? Dette gir: for(int i = 1; i < n; i*=3){ // O(log(n)) function(i); // O(log(i)) // O(log(n)^2) Tommelfingerregelestimatet er : for(int i = 1; i < n; i*=3){ // O(log(n)) function(i); // O(log(n)) // O(log(n)^2)
Skarpere estimater II for(int i = 1; i < n; i*=a){ // a > 1 function(i); // O(i^m) Anta kjøretiden for function(i), t(i) < Mi m. i gjennomløper verdiene 1, a, a 2,..., a k, der a k < an. Total kjøretid T(n) = t(1) + t(a) + + t(a k ) = M1 m + Ma m + M(a 2 ) m + + M(a k ) m = M + Ma m + M(a m ) 2 + + M(a m ) k = M(1 (am ) k+1 ) 1 a m < K(a m ) k = K(a k ) m < Ka m n m Dette betyr at T(n) vokser med orden O(n m ).
Skarpere estimater II for(int i = 1; i < n; i*=a){ function(i); // O(i^m) // O(n^m) Tommelfingerregelestimat: for(int i = 1; i < n; i*=a){ // O(log(n)) function(i); // O(n^m) // O(n^m*log(n))
Se it s learning. Oppgaver