Complexiteit of efficiëntie van algoritmen Hoe meet je deze? Tijd is geen goede maatstaf, want is afhankelijk van computer waarop algoritme wordt gedraaid. Een betere maatstaf is het aantal berekeningsstappen dat moet worden gedaan. We zijn niet zozeer geïnteresseerd in het precieze aantal stappen maar in de toename van het aantal stappen als het probleem 2 of 10 of 100 keer zo groot wordt gemaakt. for (int k=0; k < n; k++) { sum = sum + k; Als ik n 10 keer groter maak, moeten er 10 keer zo veel stappen worden gemaakt, bij 100 keer groter 100 keer meer stappen Daarom zeggen dat de complexiteit van dit algoritme O(n): orde n. Het aantal stappen neemt lineair toe met de grootte van n. Data Structuren en Algoritmen - Sorteren augustus 2013 1
Complexiteit vervolg for (int k=0; k < n; k++) { for (int m = 0; m < n; m++) {. for (int k=0; k < n; k++) { for (int m = 0; m < k; m++) {. Wat is de complexiteit van deze geneste lussen? Maakt het wat uit dat de binnenste lus in de ene tot n en de andere tot k loop? Neem bv n = 10: in bovenste: 100 stappen In onderste: 1+ 2 +.. + 10 55 stappen Dat lijkt verschillend maar: Als n = 100, in bovenste 10000 stappen in onderste 1 + 2 + 3 +.. + 100 = 5050 stappen In beide gevallen neemt het aantal stappen met +/- een factor 100 toe. Daarom voor beide is de complexiteit: O(n^2) Data Structuren en Algoritmen - Sorteren augustus 2013 2
Complexiteit vervolg Bestuderen van complexiteit van algoritmen is een belangrijke activiteit binnen de informatica. Zie bv: http://en.wikipedia.org/wiki/computational_complexity_theory De complexiteit van een algoritme zegt iets over de mogelijkheid het algoritme binnen redelijke tijd uit te voeren. Voor veel, op het oog eenvoudige, problemen zijn bv alleen exponentiële algoritmen bekend, dus O(2^n) - handelsreizigersprobleem: vindt de kortste route langs n steden - zo efficiënt mogelijk plaatsen van n componenten op een microchip - vinden van priemfactoren in een getal van lengte n (aantal decimalen). Heel belangrijk in de cryptografie. Praktisch betekent dit dat deze problemen voor n vanaf orde 100 niet meer oplosbaar zijn. Voor de eerste 2 bestaan goede benaderende algoritmen, die voor de praktijk goed genoeg zijn. Voor het vinden van priemfactoren is (nog) geen nietexponentiële methode gevonden! PKI maakt hier dankbaar gebruik van (behandeld in IBV) Data Structuren en Algoritmen - Sorteren augustus 2013 3
Sorteren Sorteren speelt een belangrijke rol in diverse algoritmen. Daarom is er behoefte aan efficiënte sorteer algoritmen. Er bestaan veel verschillende sorteer algoritmen, ieder met zijn eigen voor en nadelen. We beschouwen hier een aantal verschillende algoritmen vanuit complexiteitsoogpunt. Zie ook: http://www.sorting-algorithms.com/ Data Structuren en Algoritmen - Sorteren augustus 2013 4
Sorteren: Selection sort Het selection sort algoritme is eenvoudig: static void selsort(int a[], int n) {// n is lengte int m,h; for (int i = 0; i < n; i++) { m = i; for (int j = i+1; j < n; j++) { if (a[j] < a[m]) m = j;, h = a[i]; a[i] = a[m]; a[m] = h; Het selecteert steeds het kleinste element uit de rest van het array en zet dit vooraan. Dit algoritme heeft complexiteit n^2 (vanwege geneste lus). Dus voor een array van lengte 1000, moeten er 1.000.000 stappen doorlopen worden! Dit loopt dus heel hard op als n groter wordt! Als n = 1.000.000 dus 1.000.000.000.000 stappen! Vraag Hoelang duurt het ongeveer om een array van lengte 10.000.000 te sorteren als je 10^9 stappen per sec kan doen. Data Structuren en Algoritmen - Sorteren augustus 2013 5
Sorteren (vervolg) Gelukkig is het mogelijk efficiënter te sorteren. Een bekend snel sorteer algoritme is het Quicksort algoritme. Bij quicksort verdeel je een array herhaaldelijk in tweeën, waarbij alle elementen in het eerste deel kleiner zijn dan die in het tweede (gebeurt in split). static void qsort(int[] a, int low, int up) { int m; if (low < up) { m = split(a, low, up); qsort(a, low, m - 1); qsort(a, m + 1, up); static int split(int[] a, int low, int up) { int h, m; // hulp var m = low; // initialisatie // invariant: a[low.. m-1]< a[m] <= a[m+1..i-1] for (int i = low + 1; i <= up; i++) { if (a[i] < a[m]) { h = a[i]; a[i] = a[m+1]; a[m+1] = a[m]; a[m] = h; m++; return m; Aanroep: qsort(a,0,n-1); Data Structuren en Algoritmen - Sorteren augustus 2013 6
Sorteren (vervolg) De functie split splitst een array tussen de grenzen low en up [low..up] in twee stukken: elementen kleiner dan het eerste element (pivot element) en elementen groter dan de pivot Tijdens de uitvoering van split geldt voor i en m steeds het volgende: a[low.. m-1]< a[m] <= a[m+1..i-1] Als nu a[i] >= a[m] dan kunnen we i een opschuiven. Als a[i] < a[m] dan: schuiven we a[m+1] naar a[i], a[m] naar a[m+1] en a[i] naar a[m] (rotatie). Ga na dat we nu weer in de situatie van het plaatje zitten met i 1 opgeschoven. Wat is nu de complexiteit van qsort? split kost in de orde van n stappen (1 for lus). Hoe vaak wordt split aangeroepen: als het segment door split steeds in twee gelijke delen wordt gesplitst, roepen we split net zo vaak aan als we een segment kunnen halveren voordat de lengte van het segment 1 wordt. Dit is 2 log n. Data Structuren en Algoritmen - Sorteren augustus 2013 7
Sorteren (vervolg) De totale complexiteit wordt dus: n * 2 log n Voor een array van lengte 1000 is dit dus ongeveer: 10.000 (dus 100 * zo snel als selsort!). De voorwaarde was echter dat split een segment in ongeveer twee gelijke delen splitste. Als dit niet gebeurt is de situatie slechter. In het slechtste geval (bv. als het array al gesorteerd is) is de complexiteit weer n 2. Probeer maar eens uit op een klein voorbeeld! Let op: dit hangt natuurlijk ook af van de keuze van het pivot element. Hier kiezen we het eerste element steeds als pivot. Je kan ook het middelste element kiezen. In dat geval gaat het sorteren van een (minof-meer) al gesorteerd array veel sneller! Vraag Hoelang duurt het sorteren van ons array nu ( 2 log 10.000.000 = 23)? Data Structuren en Algoritmen - Sorteren augustus 2013 8
Merge sort Mergesort is een ander voorbeeld van een sorteer algoritme met complexiteit n* 2 log n. De belangrijkste verschillen met quicksort zijn: - quicksort sorteert in het oorspronkelijke array, mergesort heeft een hulp-array nodig. - mergesort is gegarandeerd n* 2 log n, terwijl quicksort in het slechtste geval complexiteit n 2 heeft. Het merge-sort algoritme is eenvoudiger dan quicksort. Het idee is het array herhaald in tweeën te delen tot er stukken over blijven met lengte 1 of 0. Twee gesorteerde delen kunnen ge-merged (samengevoegd) tot een gesorteerd gedeelte. De functie merge voegt de twee gesorteerde deelarrays [low..m] en [m+1..up] samen tot een nieuw gesorteerd array (schuift ze in elkaar door steeds het volgende kleinste element te kiezen). Op het einde wordt het sorteerde array weer teruggekopieerd naar het oorspronkelijk array: [low..up-1] De functie mergesort deelt het array in twee delen, sorteert deze beide recursief en merged dan deze twee gesorteerde delen. Data Structuren en Algoritmen - Sorteren augustus 2013 9
Merge sort static void merge(int a[],int h[],int low,int m,int up) { int i1 = low, i2 = m + 1, r = low; while (i1 <= m && i2 <= up) { if (a[i1] <= a[i2]) { h[r++] = a[i1++]; else { h[r++] = a[i2++]; while (i1 <= m) { h[r++] = a[i1++]; while (i2 <= up) { h[r++] = a[i2++]; for (i1 = low; i1 <= up; i1++) { a[i1] = h[i1]; static void mergesort(int a[], int h[], int low, int up) { int m; if (low < up) { m = (low + up) / 2; mergesort(a, h, low, m); mergesort(a, h, m + 1, up); merge(a, h, low, m, up); public static void main(string[] args) { int a[] = new int[]{6, 5, 4, 3, 2, 1, 7, 10, 13, 18, 20, 12, 15, 14, 9, 16, 17, 8, 19, 11; int h[] = new int[20]; mergesort(a, h, 0, 19); for (int i = 0; i < 20; i++) { System.out.print("" + a[i] + " "); System.out.println(); Data Structuren en Algoritmen - Sorteren augustus 2013 10
Heap sort Als laatste sorteer algoritme bekijken we heap sort. Heap sort heeft gegarandeerde n * 2 log n complexiteit en heeft geen extra array nodig! Het nadeel van heap sort is dat het algoritme nogal complex is. Het heap sort algoritme bestaat uit twee slagen: - In de eerste slag wordt het array in de zgn. heap conditie gebracht. - In de tweede slag wordt het array in heap conditie gesorteerd. Beide slagen hebben een complexiteit n * 2 log n. Data Structuren en Algoritmen - Sorteren augustus 2013 11
Heap conditie Om te begrijpen wat een array in heap conditie is moeten we het array voorstellen als een boom. Het array a is in de heap conditie als geldt: a[vader] >= a[zoon] voor alle vaders en zonen. Als a[i] een vader is dan zijn: a[2*i + 1] en a[2*i + 2] zijn zonen. Als a[i] een zoon is dan is a[(i-1) / 2] zijn vader (geheeltalige deling dus 5 / 2 = 2 bv). (Dit geldt niet voor a[0] (want (0-1) / 2 == 0!). Bij a[0] moeten we dus oppassen!) Data Structuren en Algoritmen - Sorteren augustus 2013 12
Algoritme om array in heap conditie te brengen static void makeheap(int a[], int n) { int i, elem; for (i = 1; i < n; i++) { elem = a[i]; moveup(a, elem, i); static void moveup(int a[], int elem, int blad) { // pre: a[0..blad-1] in heap conditie // post: a[0..blad] in heap conditie int father, son; son = blad; father = (son - 1) / 2; while (father >= 0 && elem > a[father]) { a[son] = a[father]; son = father; if (son == 0) { father = - 1; // om te stoppen else { father = (son - 1) / 2; a[son] = elem; Het idee is om beginnend in blad langs de takken van de boom omhoog te lopen totdat je of aan de top bent gekomen (elem is het grootste element), of je de juiste plaats hebt gevonden waar elem moet worden ingevoegd. Bij het omhoog lopen wordt een vader steeds in positie van de zoon geplaatst. Data Structuren en Algoritmen - Sorteren augustus 2013 13
Complexiteit van makeheap makeheap bevat een lus van lengte n - 1. moveup bevat een while-lus die van een blad in de boom tot hooguit de top loopt. Aangezien de boom niet dieper is dan 2 log n, kan moveup niet meer dan 2 log n stappen hebben. De totale complexiteit van makeheap wordt dus: n * 2 log n Data Structuren en Algoritmen - Sorteren augustus 2013 14
Sorteren van een array in heap conditie In de top van een heap bevindt zich altijd het grootste element. Dit element plaatsen we op de laatse plaats in het array waarna we het element dat op de laatste plaats stond opnieuw van bovenaf in de heap (maar nu eentje korter) plaatsen. Dit proces herhalen we totdat alle elementen aan de beurt zijn geweest. static void movedown(int a[], int elem, int last) { // pre: a[1..last] is een heap (a[0] ontbreekt) // pre: a[0..last] is een heap en elem is ingevoegd int father, son; father = 0; son = maxson(a, father, last); while (son <= last && elem < a[son]) { a[father] = a[son]; father = son; son = maxson(a, father, last); a[father] = elem; static void sortheap(int a[], int n) { int i, h; for (i = n - 1; i > 0; i--) { h = a[i]; a[i] = a[0]; movedown(a, h, i - 1); movedown plaatst een element van bovenaf in een heap h is het element dat geplaatst moet worden en i - 1 is de index van het laatste element in de heap (de heap wordt steeds 1 korter). movedown werkt op vergelijkbare manier als moveup, alleen ga je nu langs de taken naar beneden, waarbij je steeds de tak neemt van de grootste zoon en deze zoon eentje naar boven schuift. Data Structuren en Algoritmen - Sorteren augustus 2013 15
Sorteren van een array in heap conditie static int maxson(int a[], int father, int last) { int son; son = 2 * father + 1; if (son >= last) { return son; else if (a[son] < a[son + 1]) { return son + 1; else { return son; Voor de complexiteit van sortheap geldt hetzelfde verhaal als voor makeheap, deze is dus ook: n* 2 log n static void heapsort(int a[], int n) { makeheap(a, n); sortheap(a, n); Data Structuren en Algoritmen - Sorteren augustus 2013 16
Samenvatting Verschillende manieren van sorteren. Er is niet een beste. Voor korte arrays is selection sort ok qsort is vooral goed als het array een bijna random verdeling heeft mergesort is onafhankelijk van de verdeling maar heeft hulp array nodig heapsort heeft een vrij hoge overhead en daarom minder geschikt voor kleine arrays Opgave Oefen op papier met de verschillende algoritmen Volgende les moet je kunnen laten zien dat je deze op een voorbeeld kan toepassen. Data Structuren en Algoritmen - Sorteren augustus 2013 17