(10 september 2018 T02 1 ) Medan ni väntar 1. Binär sökning i sorterad array med n element kräver log 2 n försök. Hur många försök krävs i en array med 10 3, 10 6 respektive 10 9 element? 2. Skriv metoden public static String blanks(int n) som returnerar en sträng med n blanktecken. 3. Skriv metoden public static String tostring(int[] arr) som returnerar en sträng med talen i arrayen separerade med kommatecken. Hela strängen skall omges av hakparenteser. Naturligtvis rekursivt och utan användning av Javas standardmetoder.
(10 september 2018 T02 2 ) Binär sökning n är ungefär log 2 (n) 10 3 1024 10 10 6 1024 2 20 10 9 1024 3 30 Alltså: Att söka binär bland en miljard element kräver högst ca 30 jämförelser!
(10 september 2018 T02 3 ) Blanks public static String blanks(int n) { if (n == 0) return ""; else return + blanks(n-1); } Vad händer om n < 0?
(10 september 2018 T02 4 ) tostring för array public static String tostring(int[] arr, int n) { return [ + tostringhelp(arr, n) + ] ; } public static String tostringhelp(int[] arr, int n) { if (n==0) { return ""; } else if (n == 1) { return "" + arr[0]; } else { return tostringhelp(arr, n-1) + ", " + arr[n-1]; } } (Kom dock ihåg Arrays.toString( arrayreferens ))
(10 september 2018 T02 5 ) Förra föreläsningen Algoritmer Rekursion Algoritmers resurskrav (tid, utrymme) Asymptotisk notation (Ordo, Theta, Omega) Uppskattningar av reell tidsåtgång Idag: Fler och mer komplicerade exempel på konstruktion och analys av rekursiva algoritmer
(10 september 2018 T02 6 ) Rekursiv problemlösning 1. Dela upp problemet i ett eller flera delproblem av samma typ som ursprungsproblemet 2. Lös delproblemen på samma sätt 3. Kombinera lösningarna till delproblemen till en lösning på ursprungsproblemet Det måste finnas minst ett basfall Observera likheten med induktionsbevis!
(10 september 2018 T02 7 ) Exempel: Rekursiv instickssortering Sortera de n första elementen i en array: sortera a 0, a 1, a 2,..., a n 2, a n 1 sortera a 0, a 1, a 2,..., a n 2 infoga a n 1 i de sorterade elementen infoga x i a 0, a 1,..., a m om m = 0 eller x a m 1 lägg x på plats m annars flytta a m 1 till a m infoga x i a 0, a 1, a 2... a m 1 Obs: Värdet som ligger på plats m förstörs
(10 september 2018 T02 8 ) Instickssortering 0 1 2 3 n 2 n 1 3 5 1 2 9 3 8 2 6 sort(n 1) 4 1 2 2 3 3 5 6 8 9 4 4 1 2 2 3 3 4 5 6 8 9
(10 september 2018 T02 9 ) Rekursiv insticksortering: Kod /** * Sorts the elements a[0], a[1], a[2],... a[n-1] * @param a the array to be sorted * @param n number of elements to be sorted */ public static void sort(int[] a, int n) { if (n > 1) { sort(a, n-1); insert(a[n-1], a, n-1); } }
(10 september 2018 T02 10 ) Instickssortering: Rekursiv infogning /** * Inserts x in the array a[0],..., a[m]. * * Precondition: a[0] <= a[1] <=... <= a[m-1] * Postcondition: a[0] <= a[1] <=... <= a[m-1] <= a[m] * * The old value of a[m] is destroyed */ public static void insert(int x, int[] a, int m) { assert a.length > m; if (m == 0 x >= a[m-1]) a[m] = x; else { a[m] = a[m-1]; insert(x, a, m-1); } }
(10 september 2018 T02 11 ) Några nya begrepp precondition: Förhållande som förutsätts gälla när koden börjar postcondition: Förhållanden som ska gälla när koden har exekverat färdigt. Hjälper oss att resonera kring koden och övertyga oss om att den är korrekt. assert: En Java-konstruktion för att verifiera något som ska vara sant. Om villkoret inte är uppfyllt avbryts programmet. Fungerar också som dokumentation! Bra att använda i programutvecklingen! Kan slås av och på via flagga. DrJava har det påslaget som standard.
(10 september 2018 T02 12 ) Analys av insert Den centrala operationen är testen (m==0 x >= a[m-1]). Låt s(n) stå för antalet gånger den utförs vid anropet insert(x, a, n) Bästa och värsta fall: respektive s(n) = 1 s(n) = s(n 1) + 1 = s(n 2) + 1 + 1 =... = s(0) + n = 1 + n och i genomsnitt s(n) 1 + n/2
Analys av sort Låt t(n) vara antalet gånger som den centrala jämförelsen görs. Då blir t(n) = t(n 1) + s(n 1) Således bästa fall: t(n) = t(n 1) + 1 = t(n 2) + 1 + 1 =... = t(1) + n 1 = n 1 Värsta fall: t(n) = t(n 1) + n = t(n 2) + (n 1) + n = 1 + 2 + 3 + + (n 1) = n(n 1)/2 Genomsnitt: t(n) = n(n 1)/4 (10 september 2018 T02 13 )
(10 september 2018 T02 14 ) Fråga Antag att det tar 1 sek att sortera 10 3 slumptal med instickssorteringen. Hur lång tid kommer det att ta att sortera 10 4, 10 5, respektive 10 6 tal? Svar: 10 2, 10 4 respektive 10 6 sekunder. Hur lång tid är 10 6 sekunder? Drygt 11 dygn
(10 september 2018 T02 15 ) Urvalssortering Samma rekursionsfall som i instickssorteringen men men låt största och sista elementet byta plats före rekursionanropet. sortera a 0, a 1, a 2,..., a n 2, a n 1 byt plats på a n 1 och det största av elementen a 0,..., a n 1 sortera a 0, a 1, a 2,..., a n 2
(10 september 2018 T02 16 ) Urvalssortering max 0 1 2 3 n 1 3 5 1 2 9 3 8 2 6 4 swap 3 5 1 2 4 3 8 2 6 9 sort(n 1) 1 2 2 3 3 4 5 6 8 9
(10 september 2018 T02 17 ) Urvalssortering: kod. Läs själva! x public static void recursiveselectionsort(int [] a, int n) { if (n>1) { int indmax = indmax(a, n); int x = a[n-1]; a[n-1] = a[indmax]; a[indmax] = x; recursiveselectionsort(a, n-1); } }
(10 september 2018 T02 18 ) Urvalssortering: Hitta index för största. Läs själva! public static int indmax(int[] a, int n) { assert n>0; if (n == 1) { return 0; } else { int k = indmax(a, n-1); if (a[k] >= a[n-1]) { return k; } else { return n-1; } } }
(10 september 2018 T02 19 ) Iterativ urvalssortering Visar också att man kan leta minsta i stället för största... public static void iterativeselectionsort(int[] a) { for (int i=0; i<a.length; i++) { int indmin = i; for (int j=i+1; j<a.length; j++) { if (a[j] < a[indmin]) { indmin = j; } } int x = a[i]; a[i] = a[indmin]; a[indmin] = x; } } Övning: Skriv varianten som letar minsta i stället för största rekursivt
(10 september 2018 T02 20 ) Urvalssortering: Analys Det som görs mest av allt är jämförelsen a[j] < a[indmin] som görs O(n 2 ) gånger där n är antalet element (inses lätt). Hitta största av n tal: n 1 jämförelser Hitta största av n 1 tal: n 2 jämförelser Totalt: (n 1) + (n 2) + (n 3) +... + 2 + 1 = n 2 (n 1) = 0.5n2 0.5n som är Θ(n 2 ). Observera att detta gäller i bästa fall, i genomsnitt och i värsta fall.
(10 september 2018 T02 21 ) Sortering med mergesort Mergesort: 1. Dela mängden i två lika stora delar 2. Sortera delarna var för sig 3. Sammanfoga delarna
(10 september 2018 T02 22 ) Illustration av mergesort n
(10 september 2018 T02 23 ) Illustration av mergesort n/2 n/2
(10 september 2018 T02 24 ) Illustration av mergesort Sorteras var för sig
Illustration av mergesort (10 september 2018 T02 25 )
Illustration av mergesort (10 september 2018 T02 26 )
(10 september 2018 T02 27 ) Illustration av mergesort Till slut:
(10 september 2018 T02 28 ) Analys av mergesort Löser ett problem av storlek n genom att lösa två problem av storlek n/2 och sammanfoga lösningarna till delproblemen. Tiden för sammanfogningen är direkt proportionell mot n. Låt t(n) beteckna tiden att sortera n element. Då gäller t(n) = { c om n = 0, 2t(n/2) + dn om n > 0. där c och d är obekanta konstanter.
(10 september 2018 T02 29 ) Analys av mergesort Om n är en jämn 2-potens, n = 2 k så gäller t(n) = 2t(n/2) + dn = = 2(2t(n/4) + dn/2) + dn = = 4t(n/4) + dn + dn =... = 2 k t(n/2 k ) + dnk = = nt(1) + dn log n dvs tiden är Θ(n log n) som är bra! (Gäller även om n inte är en jämn 2-potens)
(10 september 2018 T02 30 ) Exempel: Quicksort Idé: 1. Ordna om elementen i arrayen så att små ligger till vänster och stora till höger. 2. Sortera de små element för sig och de stor för sig (rekursivt) Skillnaden mot mergesort är att vi ordnar om elementen innan vi applicerar den metoden rekursivt på delarna. Nackdel: Arbetsammare att skapa delproblemen Fördel: Ingen samsorteringsfas
(10 september 2018 T02 31 ) Quicksort Hur vet man vilka element som är små? Man chansar! Väljer ett element på måfå som delningselement p. Grunden i quicksort är den så kallade partitioneringen (quicksort kallas ibland också partition sort). 1 k k + 1 n p p Bra om k blir nära n/2 och dåligt om k blir nära 1 eller n. Partitioneringen appliceras rekursivt på de små respektive de stora talen.
(10 september 2018 T02 32 ) Quicksort i array: illustration 12 3 17 42 7 5 9 4 14 13 p: 12 3 17 42 7 5 9 4 14 13 4 3 17 42 7 5 9 14 13 4 3 42 7 5 9 17 14 13 4 3 9 42 7 5 17 14 13 partitionering 4 3 9 7 5 42 17 14 13 4 3 9 5 7 42 17 14 13 4 3 9 5 7 12 42 17 14 13 små värden stora värden 3 4 5 7 9 12 13 14 17 42 sortering av delarna rekursivt
(10 september 2018 T02 33 ) Quicksort: kommentarer Mycket snabb sorteringsmetod! Hur väljer man pivot-element? Hur gör man när partitionerna blir små? Tyvärr ej stabil Beräkning av median, kvartiler...
(10 september 2018 T02 34 ) Fibonacci-talen Fibonaccitalen F n definieras enligt 0 om n = 0, F n = 1 om n = 1, F n 1 + F n 2 om n > 1. i kod: public static long fib(int n) { if ( n==0 ) return 0; else if ( n==1 ) return 1; else return fib(n-1) + fib(n-2); }
(10 september 2018 T02 35 ) Fibonacci: Hur beror tiden av n? Låt t(n) vara antalet additioner som görs för att beräkna den n : te Fibonacci-talet. t(n) = t(n 1) + t(n 2) + 1 Man kan visa (se rekursionsmaterialet) att t(n) c 1.618 n dvs tiden växer exponentiellt med n. Hopplöst för stora n! Fråga: Varför blir det så dåligt när det går att lösa så mycket bättre? Svar: Man kommer att lösa samma problem många gånger.
(10 september 2018 T02 36 ) Exempel: Brickleken A B C Traven skall flyttas från A till C Brickor får flyttas hur som helst mellan platserna A, B och C men 1. endast en bricka i taget får flyttas och 2. man får inte lägga en större bricka på en mindre.
(10 september 2018 T02 37 ) Brickleken Beteckna problemet att flytta n brickor från en plats till en annan med h(n) h(n) n A B C
(10 september 2018 T02 38 ) Brickleken Eftersom understa brickan på A skall flyttas till C så måste vi först lösa problemet att flytta de n 1 översta brickorna från A till B: h(n 1) n 1 A B C Det är uppenbarligen samma typ av problem som ursprungsproblemet men lite mindre.
(10 september 2018 T02 39 ) Brickleken h(n 1) n 1 A B C
(10 september 2018 T02 40 ) Brickleken När det är gjort kan vi flytta den understa brickan från A till C: h(1) n 1 A B C
(10 september 2018 T02 41 ) Brickleken När det är gjort skall vi flytta traven på B till C: h(n 1) n 1 A B C också det ett lite mindre problem än ursprungsproblemet.
(10 september 2018 T02 42 ) Brickleken Således har vi löst ursprungsproblemet med n brickor genom att 1. flytta n 1 brickor från A till B med hjälp av C, 2. flytta 1 bricka från A till C (utan hjälp av B) och 3. flytta n 1 brickor från B till C med hjälp av A Koden (i det närmaste trivial... ) void bricklek(char from, char to, char help, int n) {... } Anropas för vårt problem med bricklek( A, C, B, n) (ta inte alltför stort n... ) DrJava
(10 september 2018 T02 43 ) Analys av brickleken Problem av storleken n löses genom att lösa två problem av storleken n 1 och ett problem av storleken 1. Således gäller för erforderligt antal brickförflyttningar b(n): b(n) = 2b(n 1) + 1 = Hopplöst för stora n! = 2(2b(n 2) + 1) + 1 = 4b(n 2) + 2 + 1 = = 2 k b(n k) + 2 k 1 + 2 k 2 + + 2 + 1 = = 2 n 1 b(1) + 2 n 2 + + 2 + 1 = = 2 n 1 1 + 2 n 2 + + 2 + 1 = 2 n 1
(10 september 2018 T02 44 ) Exempel: Växlingsproblemet Antag att vi har mynt/sedlar i följande valörer: 100, 50, 10, 5 och 1. På hur många sätta kan man växla ett visst belopp? Ex: Beloppet 12 kan växlas på 4 sätt: 1 10 + 2 1, 2 5 + 2 1, 1 5 + 7 1 och 12 1, Hur kan vi skapa mindre (dvs enklare) problem? Två sätt att skapa mindre problem: Mindre belopp Färre mynt
(10 september 2018 T02 45 ) Växlingsproblemet Vi kan dela in mängden möjliga lösningar i två disjunkta mängder: 1. de som inte använder ett visst mynt och 2. de som använder ett visst mynt Alltså: Antalet sätt att växla a kronor med myntsorterna d 1, d 2,..., d n är 1. antalet sätt att växla med mynten d 1, d 2,..., d n 1 plus 2. antalet sätt att växla a d n med mynten d 1, d 2,..., d n
(10 september 2018 T02 46 ) Växlingsproblemet Kodskiss: int static count(int a, int n) { return count(a, n-1) + count(a-d[n], n); } Arrayen d med valörerna förutsätts finnas som en klassvariabel. Basfall? a = 0 lyckad växling räkna a < 0 misslyckad växling räkna ej n = 0 misslyckad växling räkna ej
(10 september 2018 T02 47 ) Växlingsproblemet Slutligt program: int static count(int a, int n) { if (a == 0) return 1; else if (a < 0 n == 0) return 0; else return count(a, n-1) + count(a-d[n], n); } Förvånansvärt enkelt program för ett icke-trivialt problem! Trädrekursivt med exponentiell tillväxt. Går att motverka.
(10 september 2018 T02 48 ) Trädrekursiva algoritmer Brickleken: Tiden växer exponentiellt (Θ(2 n )). Hopplöst för stora n men bästa möjliga. Fibonaccitalen: Tiden växer exponentiellt (Ω(1.6 n )) Hopplöst för stora n men mycket effektivare algoritmer finns. Mergesort och Quicksort: Tiden växer som Θ(n log n). Bästa möjliga generella algoritm! Växlingsproblemet Tiden växer exponentiellt men motmedel finns.
(10 september 2018 T02 49 ) Hur vet man om en rekursiv lösning är effektiv? Rekursiva lösningar kan vara mycket effektiva men också mycket ineffektiva Beräkningsarbetet beror på arbetet att dela upp i delproblem, antalet delproblem, delproblemens storlek (ofta avgörande) samt arbetet som krävs för att kombinera lösningarna till delproblemen. Det finns problem som är hopplösa! Trädrekursion är OK under förutsättning att delproblemen är betydligt mindre är ursprungsproblemet.
(10 september 2018 T02 50 ) Varför rekursion? Generell problemlösning Lätt att hitta en lösning (när man väl lärt sig tekniken) Lätt att hitta effektiva lösningar Naturligt i många sammanhang ( derivatan av en summa är summan av derivatorna ) Bra på rekursivt definierade strukturer Men: Lätt att hitta hopplöst ineffektiva lösningar
(10 september 2018 T02 51 ) Varför använder man begreppen O, Ω och Θ? Enkel karakteristik en algoritms uppförande (tid/utrymme) för stora problem utan detaljer och oberoende av programmeringsspråk, dator,... När använder man O, när använder man Θ och när använder man Ω? Θ säger mest O är en övre begränsning (som inte nödvändigtvis är tät ) Ω är en undre begränsning.
(10 september 2018 T02 52 ) Behöver man bry sig om hur effektivt ett program är? Är frågan överhuvud taget relevant när datorerna bli snabbare och snabbare? Ja! Det finns och kommer under överskådlig tid finnas många tillämpningar där frågan är högst relevant! Ett axplock: fysik och teknik : aerodynamik, väderprognoser biologi : bioinformatik, genomsekvensering realtidssystem : robotar, självkörande bilar, basstationer i mobiltelefoni animering : datorspel, filmindustri bildstabilisering vid filmning informationssökning : google
(10 september 2018 T02 53 ) Men Viktigare att programmet är korrekt än att det går fort! Offra inte klarhet för små effektivitetsvinster! Offra inte enkelhet för små effektivitetsvinster! Offra inte modifierbarhet för små effektivitetsvinster! Om programmet är för långsamt: Hitta en bättre algoritm!