Föreläsning 5 Val av algoritm och datastruktur Innehåll Algoritmer och effektivitet Att bedöma och jämföra effektivitet för algoritmer Det räcker inte med att en algoritm är korrekt – den måste även producera resultat inom rimlig tid. Begreppet tidskomplexitet Både val av algoritm och datastruktur påverkar effektiviteten. Undervisningsmoment: föreläsning 5, övningsuppgifter 6 Vi måste kunna beräkna effektiviteten (tidsåtgången). Avsnitt i läroboken: 2.4 I 2:a upplagan: 2.4 PFK (Föreläsning 5) VT 2017 1 / 37 Analys av algoritmer VT 2017 2 / 37 Tidsåtgång och problemets storlek Effektivitet (tidsåtgång) Tiden det tar att lösa ett problem är oftast en strängt växande funktion av problemets storlek. tidsskomplexitet - ett mått på hur exeveringstiden växer med problemets storlek Det tar längre tid att summera 100 000 tal än att summera 100 tal. Minneskrav Vissa enkla problem kan dock lösas på konstant tid oavsett storleken. rumskomplexitet Om vi t.ex. har en vektor med n tal kan vi alltid ta reda på det i:e talet i vektorn på konstant tid oavsett hur stort n är. Exempel: int nbr = a[i]; Svårighetsgrad Ju mer komplicerad desto svårare att översätta till program och underhålla. PFK (Föreläsning 5) PFK (Föreläsning 5) VT 2017 3 / 37 PFK (Föreläsning 5) VT 2017 4 / 37 Tidskomplexitet Beräkna tidskomplexitet Räkna (ungefär) hur många basala operationer som utförs i algoritmen. Tidskomplexitet T(n) är ett mått på hur tidsåtgången växer med problemets storlek n. Med basal operation menas en operation som tar konstant tid på en dator, t.ex. en aritimetisk operation eller en jämförelseoperation. Tidskomplexiteten uttrycks som en funktion av problemets storlek. Med tidskomplexitet kan vi jämföra olika algoritmers effektivitet. Tidskomplexiteten uttrycks så att vi är oberoende av vilken dator som exekverar algoritmen. Vi behöver inte exekvera algoritmerna för att kunna jämföra deras effektivitet. låg tidskomplexitet () effektiv () billig, låg kostnad hög tidskomplexitet () dålig effektivitet () dyr, hög kostnad PFK (Föreläsning 5) VT 2017 Exempel: Antag att vektorn a innehåller n tal som ska summeras. for (int i = 0; i < a.length; i++) { sum += a[i]; } Satsen sum += a[i]; utförs n gånger och algoritmen får då T (n) = n. 5 / 37 Tidskomplexitet och verklig tidsåtgång PFK (Föreläsning 5) VT 2017 6 / 37 Diskutera Vilken tidskomplexitet får följande metod: Vi har räknat ut att tidskomplexiteten T (n) för summeringen av n tal är n (linjär tidskomplexitet). Verklig tidsåtgång t(n) blir då c ⇤ n eller c ⇤ T (n), där c är tidsåtgången för additionerna och tilldelningarna på den dator där summeringen exekveras. Exekveringstiden växer linjärt med antal element som ska summeras. Vi har olika verklig tid på olika datorer, men tidskomplexiteten beräknad på detta sätt är densamma för alla. PFK (Föreläsning 5) VT 2017 public static int max(int[] a) { int max = Integer.MIN_VALUE; for (int i = 0; i < a.length; i++) { if (a[i] > max) { max = a[i]; } } return max; } Antag att det vid en provkörning tog 17 ms att exekvera metoden med en vektor med 1000000 tal. Uppskatta hur lång tid det skulle ta att exekvera metoden om man fördubblar antal element i vektorn. 7 / 37 PFK (Föreläsning 5) VT 2017 8 / 37 Beräkna tidskomplexitet Diskutera Exempel Beräkna tidskomplexiteten för att summera talen i en kvadratisk matris m: for (int i = 0; i < m.length; i++) { for (int k = 0; k < m[i].length; k++) { sum += m[i][k]; } } Metoden nedan undersöker om alla element i vektorn a är unika. Exempel: Om a = {1, 4, 2, 9, 7} ska resultatet bli true. Om a = {1, 4, 2, 9, 2} ska resultatet bli false. public static boolean allUnique(int[] a) { boolean unique = true; for (int i = 0; i < a.length - 1; i++) { for (int k = i + 1; k < a.length; k++) { if (a[i] == a[k]) { unique = false; } } } return unique; } Antag att matrisen innehåller n x n element. De satser som utförs flest gånger är de som finns i den innersta for-loopen. Satsen sum += m[i]; utförs n * n gånger och algoritmen får då T (n) = n2 (kvadratisk tidskomplexitet). Tidskomplexitet? PFK (Föreläsning 5) VT 2017 9 / 37 PFK (Föreläsning 5) Tidskomplexitet Aritmetisk summa Undersök om alla element är unika Repetition från matematiken i-värde för yttre for-loopen 0 1 ... n–4 n–3 n–2 Antal ggr satserna i inre for-loopen utförs n-1 n-2 ... 3 2 1 D.v.s. T (n) = n2 /2 PFK (Föreläsning 5) 1 = n(n 10 / 37 Antag att skillnaden mellan två på varandra följande tal i en följd är konstant. Då kan vi beräkna summan av talen genom formeln: a1 + a2 + a3 + . . . + an = n(a1 + an )/2 Då blir 1 + 2 + 3 + . . . + n = n(1 + n)/2 och Totalt antal gånger satserna i inre for-satsen utförs är: 1 + 2 + 3 + ... + n VT 2017 1 + 2 + 3 + ... + n 1)/2 1 = (n 1)(1 + n 1)/2 = (n 1)n/2 n/2 vilket är ⇡ n2 /2 för stora n-värden VT 2017 11 / 37 PFK (Föreläsning 5) VT 2017 12 / 37 Förbättrad algoritm Tidskomplexitet Undersök om alla element är unika Undersök om alla element är unika Vi bör avbryta algoritmen så fort vi hittat en dubblett: För (den oförbättrade) algoritmen fick vi fram uttrycket T (n) = n(n 1)/2 = n2 /2 n/2 för tidskomplexiteten. public static boolean allUnique(int[] a) { for (int i = 0; i < a.length - 1; i++) { for (int k = i + 1; k < a.length; k++) { if (a[i] == a[k]) { return false; } } } return true; } För stora värden på n dominerar den kvadratiska termen. T (n) ⇡ n2 /2 för stora värden på n. Nu blir det svårare att beräkna exakt hur många gånger jämförelsen (a[i] == a[k] utförs. Det beror på indata. Enklast är att räkna på värsta fallet som här inträffar då inga dubbletter finns. Vi får samma tidskomplexitet som för den oförbättrade algoritmen. PFK (Föreläsning 5) VT 2017 13 / 37 Uttrycka tidskomplexitet med hjälp av Ordo T (n) = n(n 45 4 950 499 500 49 995 000 1)/2 n2 /2 50 5 000 500 000 50 000 000 T (n)/(n2 /2) 0.9 0.99 0.999 0.9999 PFK (Föreläsning 5) VT 2017 14 / 37 Ordobegreppet För att uttrycka hur en algoritms tidskomplexitet uppför sig vid stora värden på n kan vi använda Ordo-notation. Beräkna tidskomplexiteten genom att räkna hur många gånger de dominerande operationerna utförs. Exempel: T (n) = n2 /2 n/2 |T (n)| cf (n) för alla n > n0 För alla polynom T(n) av grad k: Detta ger en övre gräns för tidskomplexiteten. T (n) = O(n2 ) betyder att för stora värden på n understiger den verkliga exekveringstiden cn2 . VT 2017 Ordo-begeppet från matematiken ger oss möjlighet att begränsa en funktion ”uppåt”. En funktion T (n) är O(f (n)) (uttalas ”T är ordo f”) om det finns konstanter n0 och c så att Stryk sedan allt utom den dominerande termen. Stryk även ev. konstanter på den dominerande termen. Exempel: T (n) = O(n2 ) PFK (Föreläsning 5) n 10 100 1 000 10 000 15 / 37 T (n) = ak ⇤ nk + ak 1 ⇤ nk 1 + ... gäller att de är O(nk ). PFK (Föreläsning 5) VT 2017 16 / 37 Tidskomplexitet Ordobegreppet Vi har tidigare räknat fram T (n) genom att beräkna hur många gånger de ”dominerande” operationerna utförs. I komplexitetsberäkningar förutsätts att man inte ger en större övre gräns än nödvändigt: För att ange storleksordningen på T (n) med ordonotation kan allt utom den dominerande termen strykas. Om T (n) = 2n2 + n är T (n) = O(n2 ) T (n) = dominant term + mindre termer =) T (n) = O(dominant term). T (n) = 2n2 + n =) T (n) = O(2n2 ) men det är även sant att T (n) = O(n3 ) ellerO(n4 ) eller . . . Eventuell konstant på den dominerande termen kan också strykas. T (n) = O(2n2 ) =) T (n) = O(n2 ) PFK (Föreläsning 5) VT 2017 17 / 37 Asymptotisk tidskomplexitet PFK (Föreläsning 5) VT 2017 18 / 37 Diskutera – tidskomplexitet och verklig tidsåtgång För stora värden på n gäller att verklig tidsåtgång t(n) = c ⇤ T (n) När man i stället för att ange ett exakt uttryck på tidskomplexiteten använder ordonotation brukar man tala om algoritmens asymptotiska tidskomplexitet. Problem: En algoritm har tidskomplexiteten T (n) = O(n2 ). Antag att då man undersöker om elementen i en vektor med 10000 element är unika får en exekveringstid på 20 ms. Ungefär hur lång tid tar det att exekvera programmet för n = 1000000? Man anger ju hur den asymptotiskt uppför sig för stora värden på n (problemets storlek). PFK (Föreläsning 5) VT 2017 19 / 37 PFK (Föreläsning 5) VT 2017 20 / 37 Storleksordning för funktioner Tidskomplexitet för några algoritmer När en algoritm har tidskomplexiteten T(n) = O(1), så säger man att tidskomplexiteten är konstant. På samma sätt används namn på vanligt förekommande storleksordningar för T(n) = O(f(n)), enligt tabellen: f(n) 1 logn n nlogn n2 n3 2n Sätta in ett element först i en lista med n element: O(1) Söka med binärsökning bland n sorterade element: O(logn) Söka bland n osorterade element: O(n) namn konstant logaritmisk linjär log-linjär kvadratisk kubisk exponentiell PFK (Föreläsning 5) Sortera med bättre metoder som Mergesort, Heapsort och Quicksort (som kommer senare i kursen): O(nlogn) Sortera n tal med urvalssortering, bubbelsortering eller insättningssortering: O(n2 ) Multiplicera två n x n-matriser med den vanliga metoden: O(n3 ) Vissa dekrypteringsalgoritmer (n=antal bitar i nyckeln): O(2n ) VT 2017 21 / 37 Tidskomplexitet – olika storleksordningar i praktiken PFK (Föreläsning 5) t = 1 sekund 1 000 000 62 746 1 000 100 19 t = 24 timmar 86.4 ⇤ 109 2.8 ⇤ 109 293 938 4 421 36 t = 1 år 31.5 ⇤ 1012 8 ⇤ 1011 5.6 ⇤ 106 31 593 44 VT 2017 VT 2017 22 / 37 Tidskomplexitet kan bero på indata Största värde på n för exekveringstiden t om den dominerande operationen tar 1/1 000 000 sek: T(n) n nlogn n2 n3 2n PFK (Föreläsning 5) /** Undersök om talet x finns i heltalsvektorn a. */ public static boolean contains(int[] a, int x) { for (int i = 0; i < a.length; i++) { if (a[i] == x) { return true; } } return false; // kommer vi hit fanns ej x i vektorn } Det som görs inuti for-satsen dominerar. Komplikation: Går inte att säga exakt hur många gånger den utförs. Det beror på indata. 23 / 37 PFK (Föreläsning 5) VT 2017 24 / 37 Tidskomplexitet – värsta fall och medelfall Värsta fall och medelfall – formella definitioner För algoritmer vars tidskomplexitet beror på indata brukar man ställa två frågor: Om det finns k olika instanser av storlek n och tidskomplexiteten för den i:e instansen är Ti (n) och sannolikheten för denna instans är Pi gäller: Vad är den i värsta fall för instanser av storlek n? Vad är den i medeltal för alla instanser av storlek n? W (n) = maxTi (n) X A(n) = Pi ⇤ Ti (n) Tidskomplexitet i värsta fall för instanser av storlek n brukar betecknas W (n). W står för Worst case. W (n) är ofta lika lätt att beräkna som T (n). Tidskomplexitet i medelfall för instanser av storlek n brukar betecknas A(n). A står för Average. PFK (Föreläsning 5) VT 2017 A(n) ofta svårare. För en del algoritmer okänt på grund av svårigheten att ge värden åt sannolikheter för de olika instanserna av storlek n. 25 / 37 Värsta fall för linjärsökning PFK (Föreläsning 5) VT 2017 26 / 37 Medelfall för linjärsökning Komplikation: Det finns ett oändligt antal instanser av storlek n. Om vi antar följande villkor kan vi enkelt beräkna medelfallstiden: Problemets storlek = vektorns längd. Kalla denna n, d.v.s. n = a.length. 1 x finns i vektorn. I värsta fall blir jämförelsen alltid false och for-satsen utförs då för alla i-värden från 0 till och med n–1. 2 Det är lika sannolikt att x finns på var och en av platserna. D.v.s. sannolikheten är 1/n att x finns på plats i för i = 0, 1, . . . , n–1. Satserna inuti for-satsen utförs då n gånger. 3 Alla elementen i vektorn är olika. Alltså: W (n) = n = O(n). PFK (Föreläsning 5) Vi har då minskat antal möjliga instanser av problemet till n: x finns på plats 0, x finns på plats 1, ..., x finns på plats n-1. VT 2017 27 / 37 PFK (Föreläsning 5) VT 2017 28 / 37 Medelfall för linjärsökning Tidskomplexitet för algoritmer som anropar operationer De flesta algoritmer uttrycker sig inte enbart med basala O(1)-operationer som +, – , . . . utan unyttjar mera kraftfulla operationer. i 0 1 ... i ... n–1 A(n) = X instans x == a[0] x == a[1] ... x == a[i] ... x == a[n–1] pi 1/n 1/n ... 1/n ... 1/n Ti (n) 1 2 ... i+1 ... n Exempel: Sortera en vektor och undersök sedan om alla element är unika. pi ⇤ Ti (n) = (1 + 2 + ... + n) ⇤ 1/n = (n + 1)/2 = O(n) public static boolean allUnique(int[] a) { Arrays.sort(a); for (int i = 0; i < a.length - 1; i++) { if (a[i] == a[i + 1]) { return false; } } return true; } Tidskomplexitet? PFK (Föreläsning 5) VT 2017 29 / 37 Tidskomplexitet för algoritmer som anropar operationer Metoden sort innehåller en effektiv sorteringsalgoritm med T (n) = O(nlogn). 1) ⇤ O(1) = O(n). Sammanlagd kostnad för algoritmen blir O(nlogn) + O(n). T (n) = O(nlogn) VT 2017 anropar operationerna o1 , o2 , ..., ok . Operationen oi anropas mi gånger. Operationen oi har värstafalltidskomplexiteten Wi (n). Då blir kostnaden för algoritmen i värsta fall: m1 ⇤ W1 (n) + m2 ⇤ W2 (n) + ... + mk ⇤ Wk (n) D.v.s. den operation som anropas minst antal gånger dominerar i detta fall och bestämmer tidskomplexiteten. PFK (Föreläsning 5) 30 / 37 Antag att en algoritm Algoritmen gör ett anrop av metoden sort som inte ärO(1) och gör i värsta fall n 1 jämförelser som är O(1). 1 jämförelserna kostar sammanlagt (n VT 2017 Tidskomplexitet för algoritmer som anropar operationer Antag att vektorn innehåller n element. De n PFK (Föreläsning 5) 31 / 37 Deltermer som inte dominerar detta uttryck kan sedan strykas om man bara vill ha en ordo-uppskattning. PFK (Föreläsning 5) VT 2017 32 / 37 Tidskomplexitet för algoritmer som anropar operationer Tidskomplexitet för algoritmer som anropar operationer Exempel: Antag att en vektor a innehåller n tal. I följande algoritm läggs alla unika tal från a in i en lista. List<Integer> list = new ArrayList<Integer>(); for (int i = 0; i < a.length; i++) { if (! list.contains(a[i])) { list.add(a[i]); } } for (int i = 0; i < a.length; i++) { if (! list.contains(a[i])) { list.add(a[i]); } } Antag att n = a.length. I värsta fall innehåller listan då n element. Inuti contains söks i värsta fall alla element igenom. Denna sökning har tidskomplexiteten O(n). De n anropen av contains kostar då O(n2 ). I add läggs elementet till sist. Denna operation har tidskomplexiteten O(1). I värsta fall anropas add n gånger vilket kostar O(n). Vad får algoritmen för tidskomplexitet? Sammanlagd kostnad för algoritmen blir O(n2 ) + O(n) vilket innebär att T (n) = O(n2 ). PFK (Föreläsning 5) VT 2017 33 / 37 Bättre datastruktur för att lagra en samling unika element PFK (Föreläsning 5) VT 2017 34 / 37 Diskutera Vad får följande algoritm för tidskomplexitet? En mängd (eng. Set) passar bättre än en lista om man ska lagra unika element. List<Integer> list = new LinkedList<Integer>(); ... for (int i = 0; i < list.size(); i++) { System.out.println(list.get(i)); } Set<Integer> list = new HashSet<Integer>(); for (int i = 0; i < a.length; i++) { list.add(a[i]); } Ovanstående sätt att iterera genom en länkad lista är ineffektivt. Istället ska man använda en iterator. Vilken tidskomplexitet blir det då? Dubblettkontroll sker inuti add. Metoden add i klassen HashSet har i medelfall (och i praktiken) tidskomplexiteten O(1). for (int nbr : list) { System.out.println(nbr); } Mängder och klassen HashSet behandlas senare under kursen. PFK (Föreläsning 5) VT 2017 35 / 37 PFK (Föreläsning 5) VT 2017 36 / 37 Algoritmer och effektivitet Exempel på vad du ska kunna Förklara begreppet tidskomplexitet. Beräkna tidskomplexiteten T (n) för enkla algoritmer (av den svårighetsgrad som behandlats på föreläsningar och övningar). Uttrycka tidskomplexitet med O (ordo) och förklara vad det innebär att tidskomplexiteten är O(f (n)). Förstå vad olika storleksordningar på tidskomplexiteten innebär för algoritmers användbarhet (konstant, logaritmisk, linjär, kvadratisk, exponentiell,...). Förstå och kunna använda sambandet mellan verklig tidsåtgång och teoretiskt beräknad tidskomplexitet, t(n) ⇡ c ⇤ T (n). Avgöra när en algoritms tidskomplexitet beror på indata och i sådana fall kunna beräkna tidskomplexitet för värsta fall, W (n). Kunna förklara vad som menas med tidskomplexitet i medelfall, A(n), för algoritmer vars tidskomplexitet beror på indata. PFK (Föreläsning 5) VT 2017 37 / 37