Datastrukturer D Föreläsning 2
Jämförelse mellan olika sorteringsalgoritmer n Selection sort T(n) Insertion sort T(n) 2 1 1 1 Merge sort T(n) 4 6 3-6 4-5 8 28 7-28 12-17 16 120 15-120 32-49 Analysis of Algorithms 2
Men hur lång TID tar de? Man får provköra och mäta! Men då beror det på maskin, kompilator, Man kan kanske räkna antalet exekverade maskininstruktioner? Svårt alltför låg nivå! Analysis of Algorithms 3
En uppskattning av sorteringstiden - Alla tre sorteringsalgoritmerna gör upprepade jämförelser mellan element. - Varje jämförelse tar ungefär lika lång tid att exekvera. - Exekveringstiden bör vara ungefär proportionell mot antalet jämförelser som utförs. - Det finns åtminstone en övre begränsning C på exekveringstiden för varje jämförelse, där C beror på maskin, kompilator, etc. - Exekveringstiden för selectionsort blir då T(n) < C(n(n-1)/2) Man säger att tiden påverkas av en konstant faktor C Analysis of Algorithms 4
Asymptotisk komplexitet Vi vill ha ett mått som är oberoende av konstanten C, men som ändå fångar fakta som (i) insertionsort är överlägset effektivast i bästa fallet för stora n (ii) mergesort är överlägset effektivast i värsta fallet för stora n Analysis of Algorithms 5
O-begreppet InsertionSort är linjär i bästa fall. T(n) begränsas av en linjär funktion. Man säger att den är O(n) InsertionSort är kvadratisk i värsta fall. T(n) begränsas av en kvadratisk funktion. Man säger att den är O(n 2 ) MergeSort är O(n log n) Analysis of Algorithms 6
Jämförelse mellan olika sorteringsalgoritmer n Selection sort T(n) Insertion sort T(n) 2 1 1 1 Merge sort T(n) 4 6 3-6 4-5 8 28 7-28 12-17 16 120 15-120 32-49 O(n 2 ) O(n)-O(n 2 ) O(n log n) Analysis of Algorithms 7
Pseudokod igen notation för att beskriva algoritmer utan att ge alla detaljer blandning av formell notation och naturligt språk mer lättläst än riktigt programspråk inte bundet till visst programspråk viktig abstraktionsnivå att behärska 8
Exempel på pseudokod i G&T Algorithm arraymax(a,n) Input: An array A of n elements, n > 0 Output: The maximum element of A cm A[0] for i 1 to n-1 do if A[i] > cm then cm A[i] return cm 9
Samma algoritm i Java int arraymax(int[] A, int n) { int cm = A[0]; for(int i = 1, i < n, i++) { if(a[i] > cm) cm = A[i]; } return cm; } inte så stor skillnad för Goodrich & Tamassias pseudokod är ganska Java-inspirerad! 10
Hur jämför man effektivitet? Mät tiden, vilken är snabbast? Mät på flera indata! Rita kurvor över hur tiden beror på indatastorleken! Är mätningarna jämförbara? Samma dator? Samma kompilator? 11
Räkna maskininstruktioner int arraymax(int[] A, int n) { int cm = A[0]; // 2 instruktioner for(int i = 1, i < n, i++){ // 1 + n + (n - 1) if(a[i] > cm) // 2(n - 1) cm = A[i]; // 2(n - 1) bara ibland } // return cm; // 1 } Totalt 6n 1 instruktioner i värsta fall och 4n + 1 i bästa fall. Antag instruktionerna: i = n, A[i], i < j, i++, return 12
Tidsanalys av algoritmer Uppskatta exekveringstiden genom att räkna antalet primitiva operationer som exekveras för olika indata. Uppskatta hur exekveringstiden T(n) beror på storleken n på indata. Gör asymptotisk analys av T(n). Växer den linjärt, kvadratiskt, logaritmiskt, Analysera värsta fallet, bästa fallet, medelfallet. 13
T(n) är inte entydigt bestämt T(n) = exekveringtiden n = indatastorleken 14
Linjär tidskomplexitet En algoritm har linjär tidskomplexitet omm det finns c och n 0 så att T(n) < c n, n > n 0 exekveringstiden konstanten T(n) begränsas av en linjär funktion med avseende på indatastorleken n Ex: arraymax tag c=6 och n 0 = 0 15
Tillväxttakten för exekveringen Byt kompilator, dator, etc Påverkar T(n) med en konstant faktor En linjär algoritm förblir linjär! Bara konstanten c ändras. 16
Officiell definition av O- notation T(n) är O(f(n)) betyder: Det finns positiva heltal c, n 0 så att T(n) < c f(n) för alla n > n 0 17
O-notation Linjär T(n) är O(n): T(n) < c n Logaritmisk T(n) är O(log n) T(n) < c log n Kvadratisk T(n) är O(n 2 ) T(n) < c n 2 Allmänt T(n) är O(f(n)) T(n) < c f (n) för alla n > n 0 18
Stora-O regler Om f(n) är ett polynom av grad d, så är f(n) O(n d ), dvs vi kan 1. ta bort termer av lägre grad 2. ta bort konstanter Använd minsta möjliga komplexitetsklass Säg 2n är O(n) istället för 2n är O(n 2 ) 19
Vanliga komplexitetsklasser O(1) konstant tid O(log n) logaritmisk tid O(n) linjär tid O(n log n) n log n -tid O(n 2 ) O(n 3 ) O(2 n ) kvadratisk tid kubisk tid exponentiell tid 20
Sökning i ett fält Linjär sökning - O(n) Binärsökning i ett sorterat fält är logaritmisk O(log n) 21
Linjär och logaritmisk tid 22
Generiska algoritmer Insertion sort i Haskell hade en polymorf typ Ord a => [a] -> [a] Kan vi få samma effekt i Java? Gränssnitten Comparable och Comparator Generics, nyhet i Java 5 Analysis of Algorithms 23
Insättningssortering i Java igen void insertionsort(int[] cards){ int n = cards.length; for (int i = 1; i < n; i++) for (j = i; j > 0 && cards[j] < cards[j-1]; j--) swap(cards,j,j-1); } Hur sorterar man element av andra typer? Analysis of Algorithms 24
Insättningssortering för tecken void insertionsort(char[] cards){ int n = cards.length; for (int i = 1; i < n; i++) for (j = i; j > 0 && cards[j] < cards[j-1]; j--) swap(cards,j,j-1); } Hur sorterar man strängar? Analysis of Algorithms 25
Insättningssortering för strängar void insertionsort(string[] cards) { int n = cards.length; for (int i = 1; i < n; i++) for (j = i; j > 0 && cards[j].compareto(cards[j-1]) < 0; j--) swap(cards,j,j-1); } String implementerar gränssnittet Comparable; compareto använder lexikografisk ordning. Analysis of Algorithms 26
Gränssnittet Comparable public interface Comparable { public int compareto(object o); } Låt o1 vara ett objekt av en klass som implementerar Comparable. o1.compareto(o2) < 0 omm o1 är mindre än o2 = 0 omm o1 och o2 är lika > 0 omm o1 större än o2 Klassen String implementerar Comparable. Metoden compareto använder lexikografisk ordning. Analysis of Algorithms 27
En klass för spelkort Antag att vi har definierat en klass class Card { int suit; int value; } Hur definierar vi en lämplig ordning? Analysis of Algorithms 28
En klass för spelkort med naturlig ordning class Card { int suit; int value; int compareto(card c) { return 13*(suit c.suit) + (value c.value); } } Denna klass implementerar dock inte Comparable! Varför? Analysis of Algorithms 29
En klass för spelkort som implementerar Comparable class Card implements Comparable { int suit; int value; int compareto(object o) { Card c = (Card)o; return 13*(suit c.suit) + (value c.value); } } Observera att vi måste göra en typkonvertering compareto kräver att vi kan jämföra med alla objekt, trots att man normalt bara vill jämföra med samma slags objekt. Analysis of Algorithms 30
Polymorf insättningssortering void insertionsort(comparable[] cards){ int n = cards.length; for (int i = 1; i < n; i++) for (j = i; j > 0 && cards[j].compareto(cards[j-1]) < 0; j--) swap(cards,j,j-1); } Vi kan nu sortera objekt av alla klasser som implementerar Comparable! Analysis of Algorithms 31
Polymorf sortering i Haskell insertionsort :: Ord a => [a] -> [a] class Eq a => Ord a where compare :: a -> a -> Ordering (<) :: a -> a -> Bool data Ordering = LT EQ GT Analysis of Algorithms 32
Java 5 med generics I Java 5 (2004) introducerades generiska typer och generiska metoder ( the generics framework ). Se G&T. Med hjälp av dem kan man undvika många typkonverteringar ( casts ). Javas generics motsvarar bättre polymorfa typer och funktioner i Haskell, än vad Javas polymorphism gör Analysis of Algorithms 33
Generisk version av Comparable interface Comparable<E> { int compareto(e o); } E är en godtycklig referenstyp, dvs en klass eller ett gränssnitt. Jfr vår tidigare version: interface Comparable { int compareto(object o); } Analysis of Algorithms 34
Generisk version av Comparable interface Comparable<E> {public int compareto(e o);} E är en formell typparameter. Den kan instantieras med godtycklig referenstyp (dock inte med primitiv typ). class CInt implements Comparable<Integer> { int j; public int compareto(integer o) { return j o.intvalue(); } } Analysis of Algorithms 35
Card i Java 5 class Card implements Comparable<Card> { int suit; int value; int compareto(card c) { return 13*(suit c.suit) + (value c.value); } } Vi behöver inte längre göra typkonvertering! Analysis of Algorithms 36
En generisk metod i Java 5: insättningssortering <C extends Comparable> void insertionsort(c[] cards) { int n = cards.length; for (int i = 1; i < n; i++) for (j = i; j > 0 && cards[j].compareto(cards[j-1]) < 0; j--) swap(cards,j,j-1); } Denna generiska metod kan instantieras med alla deltyper till Comparable. Analysis of Algorithms 37
Gränssnittet Comparator Gränssnittet Comparable kräver att man definierar en jämförelsemetod compareto(obj o), den naturliga ordningen mellan objekten i klassen och andra objekt. Gränssnittet Comparator kräver att man definierar en jämförelsemetod compare(obj o1,o2) mellan två godtyckliga objekt. Comparable är ett gränssnitt i java.lang. Comparator är ett gränssnitt i java.util! Analysis of Algorithms 38
Gränssnittet Comparator public interface Comparator { public int compare(object o1, Object o2); (det finns en metod till som vi inte bryr oss om) } compare(o1,o2) < 0 omm o1 är mindre än o2 = 0 omm o1 är lika med o2 > 0 omm o1 är större än o2 Analysis of Algorithms 39
En komparator för kort class CardComparator implements Comparator{ int compare(object o1, Object o2) { Card c1 = (Card)o1; Card c2 = (Card)o2; return 13*(c1.suit c2.suit) + c1.value - c2.value; } } Analysis of Algorithms 40
Polymorf sortering med Comparator void insertionsort(object[] cards, Comparator com){ int n = cards.length; for (int i = 1; i < n; i++) for (j = i; j > 0 && com.compare(cards[j],cards[j-1]) < 0; j--) swap(cards,j,j-1); } Vi kan sortera korten med avseende på olika ordningar! Analysis of Algorithms 41
En generisk metod till: sortering med Comparator <C> void insertionsort(c[] cards, Comparator<C> com) { int n = cards.length; for (int i = 1; i < n; i++) for (j = i; j > 0 && com.compare(cards[j],cards[j-1]) < 0; j--) swap(cards,j,j-1); } Analysis of Algorithms 42
Sammanfattning Vi har jämfört effektiviteten hos olika sorteringsalgoritmer genom att räkna antalet jämförelser. Vi har diskuterat både bästa och värsta fallet. Vi har bestämt asymptotiska komplexiteter (Okomplexiteter). Vi har skrivit polymorfa sorteringsmetoder i Java genom att använda gränssnitten Comparable och Comparator. Vi har definierat generiska typer och generiska metoder i Java 5. Vi har definierat polymorfa sorteringsmetoder i Haskell genom att använda typklassen Ord. Analysis of Algorithms 43
Logaritmer Den reella logaritmfunktionen är invers till exponentialfunktionen: log 2 n = x omm n = 2 x Heltalslogaritmen log 2 n är antalet gånger som man kan halvera n. (Avrunda nedåt när man halverar udda tal.) Hur många gånger kan man halvera 9? 44
Logaritmer med olika bas log 3 n = x omm n = 3 x log 3 n är antalet gånger man kan tredela n log 10 n = x omm n = 10 x ln n = log e n = x omm n = e x 45
O(log n) är oberoende av basen Varför? O(log b n) = O(log c n) log b n = (log b c) (log c n) Skiljer bara med en konstant faktor log b c (den beror inte på n)! Exempel: log 2 n = (log 2 4) (log 4 n) = 2 log 4 n 46
Varför är log b n = (log b c) (log c n)? log b n = (log b c)(log c n) omm b (log b n) = b (log b c)(log c n) 47
Rekursion Rekursion: när en metod anropar sig själv Fakultetsfunktionen n! = 1 2 3 (n-1) n Rekursiv funktion i Haskell factorial n = if n == 0 then 1 else n * factorial(n-1) Rekursiv metod i Java public static int factorial(int n) { if (n == 0) return 1; // base case } else return n * factorial(n-1); // recursive case 48
Ett exempel på linjär rekursion Algorithm LinearSum(A, n): Input: An integer array A and an integer n, such that A has at least n elements Output: The sum of the first n integers in A if n = 1 then return A[0] else return LinearSum(A, n - 1) + A[n - 1] anropal return 15 + A [ 4 ] = 15 + 5 = 20 LinearSum ( A, 5 ) anropa return 13 + A [ 3 ] = 13 + 2 = 15 LinearSum ( A, 4 ) anropa return 7 + A [ 2 ] = 7 + 6 = 13 LinearSum ( A, 3 ) anropa return 4 + A [ 1 ] = 4 + 3 = 7 LinearSum ( A, 2 ) Haskell: linearsum [a] = a linearsum (a:as) = a + linearsum as anropa return A [ 0 ] = 4 LinearSum ( A, 1 ) A= 4 3 6 2 5 49
Ett exempel till på linjär rekursion Algorithm ReverseArray(A, i, j): Input: An array A and nonnegative integer indices i and j Output: The reversal of the elements in A starting at index i and ending at j if i < j then Swap A[i] and A[j] ReverseArray(A, i + 1, j - 1) return Beräkningen har bara ett rekursivt anrop och skillnaden mellan indexen i och j minskar hela tiden. När i < j terminerar algoritmen. 50
Binär Rekursion Binär rekursion inträffar när vi har två rekursiva anrop för varje icke-basfall. Risk för att antalet anrop växer exponentiellt! 51
Beräkna fibonaccital Fibonaccitalen är rekursivt definierade: F 0 = 0 F 1 = 1 F i = F i-1 + F i-2 för i > 1. Som en rekursiv algoritm i pseudokod Algorithm BinaryFib(k): Input: Nonnegative integer k Output: The kth Fibonacci number F k if k < 2 then return k else return BinaryFib(k - 1) + BinaryFib(k - 2) 52
Fibonacci i Haskell fib 0 = 1 fib 1 = 1 fib n = fib (n - 1) + fib (n 2) Tfib 0 = 1 Tfib 1 = 1 Tfib n = Tfib (n 1) + Tfib (n 2) + 1 53
Analys Låt n k stå för antalet funktionsanrop som BinaryFib(k) gör. n 0 = 1 n 1 = 1 n 2 = n 1 + n 0 + 1 = 1 + 1 + 1 = 3 n 3 = n 2 + n 1 + 1 = 3 + 1 + 1 = 5 n 4 = n 3 + n 2 + 1 = 5 + 3 + 1 = 9 n 5 = n 4 + n 3 + 1 = 9 + 5 + 1 = 15 n 6 = n 5 + n 4 + 1 = 15 + 9 + 1 = 25 n 7 = n 6 + n 5 + 1 = 25 + 15 + 1 = 41 n 8 = n 7 + n 6 + 1 = 41 + 25 + 1 = 67. Vi ser att antalet anrop mer än fördubblas för vartannat värde på n k. D.v.s., n k > 2 k/2. Algoritmen är exponentiell! 54
Ett exempel på binär rekursion som trots allt är O(n) Problem: addera alla tal i fältet A: Algorithm BinarySum(A, i, n): Input: An array A and integers i and n Output: The sum of the n integers in A starting at index i if n = 1 then return A[i ] return BinarySum(A, i, n/ 2) + BinarySum(A, i + n/ 2, n/ 2) Exempel trace: 0, 8 0, 4 4, 4 0, 2 2, 2 4, 2 6, 2 0, 1 1, 1 2, 1 3, 1 4, 1 5, 1 6, 1 7, 1 55
En linjär fibonaccialgoritm Använd linjär rekursion: Algorithm LinearFibonacci(k): Input: A positive integer k Output: Pair of Fibonacci numbers (F k, F k-1 ) if k = 1 then return (k, 0) else (i, j) = LinearFibonacci(k - 1) return (i +j, i) Är O(k). Skriv denna algoritm i Haskell! 56