Algoritmanalys. Inledning. Informationsteknologi Malin Källén, Tom Smedsaas 1 september 2016

Relevanta dokument
Rekursion och induktion för algoritmkonstruktion

Rekursion och induktion för algoritmkonstruktion

Rekursion och induktion för algoritmkonstruktion

Medan ni väntar. 2. Skriv metoden. 3. Skriv metoden. Naturligtvis rekursivt och utan användning av Javas standardmetoder.

Sökning och sortering

Föreläsning 1 Datastrukturer (DAT037)

Föreläsning 5 Innehåll

Programkonstruktion och Datastrukturer

TDDI16 Datastrukturer och algoritmer. Algoritmanalys

Programmering för språkteknologer II, HT2014. Rum

Algoritmer och effektivitet. Föreläsning 5 Innehåll. Analys av algoritmer. Analys av algoritmer Tidskomplexitet. Algoritmer och effektivitet

FÖRELÄSNING 2, TDDC74, VT2018 BEGREPP PROBLEMLÖSNING MED HJÄLP AV FALLANALYS PROBLEMLÖSNING MED HJÄLP AV REKURSION

TDDC30. Objektorienterad programmering i Java, datastrukturer och algoritmer. Föreläsning 5 Jonas Lindgren, Institutionen för Datavetenskap, LiU

Algoritmer, datastrukturer och komplexitet

Föreläsning 5 Innehåll. Val av algoritm och datastruktur. Analys av algoritmer. Tidsåtgång och problemets storlek

Introduktion till algoritmer - Lektion 4 Matematikgymnasiet, Läsåret Lektion 4

SCB :-0. Uno Holmer, Chalmers, höger 2 Ex. Induktiv definition av lista. // Basfall

Föreläsning 6 Innehåll. Rekursion. Rekursiv problemlösning Mönster för rekursiv algoritm. Rekursiv problemlösning. Rekursion. Rekursivt tänkande:

Tommy Färnqvist, IDA, Linköpings universitet. 2 Rekursion i C Implementation av rekursion Svansrekursion En till övning...

Datastrukturer D. Föreläsning 2

Föreläsning 11 Datastrukturer (DAT037)

Föreläsning 7 Innehåll. Rekursion. Rekursiv problemlösning. Rekursiv problemlösning Mönster för rekursiv algoritm. Rekursion. Rekursivt tänkande:

Föreläsning 9 Innehåll. Söndra och härska. Fibonaccitalen. Söndra och härska. Divide and conquer teknik för att konstruera rekursiva algoritmer.

Datastrukturer, algoritmer och programkonstruktion (DVA104, VT 2015) Föreläsning 6

Föreläsning 5. Rekursion

Lösning av några vanliga rekurrensekvationer

Tommy Färnqvist, IDA, Linköpings universitet

Datastrukturer. föreläsning 2

Exempel: Förel Rekursion III Nr 14. Uno Holmer, Chalmers,

Föreläsning 2 Datastrukturer (DAT037)

Magnus Nielsen, IDA, Linköpings universitet

Programmering för Språkteknologer II. Innehåll. Associativa datastrukturer. Associativa datastrukturer. Binär sökning.

Föreläsning 9 Innehåll. Söndra och härska. Fibonaccitalen. Söndra och härska. Divide and conquer teknik för att konstruera rekursiva algoritmer.

Bakgrund och motivation. Definition av algoritmer Beskrivningssätt Algoritmanalys. Algoritmer. Lars Larsson VT Lars Larsson Algoritmer 1

public static void mystery(int n) { if (n > 0){ mystery(n-1); System.out.print(n * 4); mystery(n-1); } }

Algoritmanalys. Genomsnittligen behövs n/2 jämförelser vilket är proportionellt mot n, vi säger att vi har en O(n) algoritm.

Föreläsning 4 Datastrukturer (DAT037)

BEGREPP HITTILLS FÖRELÄSNING 2 SAMMANSATTA UTTRYCK - SCHEME DATORSPRÅK

TDDC30. Objektorienterad programmering i Java, datastrukturer och algoritmer. Föreläsning 6 Jonas Lindgren, Institutionen för Datavetenskap, LiU

Algoritmer, datastrukturer och komplexitet

Datastrukturer och algoritmer

n (log n) Division Analysera skolboksalgoritmen för division (trappdivision). Använd bitkostnad.

Föreläsning 11: Rekursion

Föreläsning Datastrukturer (DAT037)

Föreläsning 2 Datastrukturer (DAT037)

Översikt. Stegvis förfining. Stegvis förfining. Dekomposition. Algoritmer. Metod för att skapa ett program från ett analyserat problem

Algoritmer. Två gränssnitt

Algoritmer, datastrukturer och komplexitet

Föreläsning 8 Innehåll

Algoritmer och datastrukturer H I HÅKAN S T R Ö M B E R G N I C K L A S B R A N D E F E L T

Objektorienterad programmering E. Algoritmer. Telefonboken, påminnelse (och litet tillägg), 1. Telefonboken, påminnelse (och litet tillägg), 2

Föreläsning 1, vecka 7: Rekursion

Föreläsning 4 Datastrukturer (DAT037)

samt lite algoritmer en kortfattad introduktion för studenter på Intro:DV

Tommy Färnqvist, IDA, Linköpings universitet

Tentamen med lösningsförslag Datastrukturer för D2 DAT 035

Lösningsförslag för tentamen i Datastrukturer (DAT037) från

Föreläsning 11 Innehåll

Föreläsning 9: Talteori

Anmälningskod: Lägg uppgifterna i ordning. Skriv uppgiftsnummer (gäller B-delen) och din kod överst i högra hörnet på alla papper

Programmering II (ID1019) :00-11:00

Tentamen i Algoritmer & Datastrukturer i Java

SORTERING OCH SÖKNING

Asymptotisk komplexitetsanalys

Föreläsning 13. Rekursion

Lägg uppgifterna i ordning. Skriv uppgiftsnummer och din anmälningskod överst i högra hörnet på alla papper.

Dekomposition och dynamisk programmering

Teoretisk del. Facit Tentamen TDDC (6)

Sortering. Brute-force. Sortering Ordna element enligt relation mellan nyckelvärden

TDDC30. Objektorienterad programmering i Java, datastrukturer och algoritmer. Föreläsning 10 Erik Nilsson, Institutionen för Datavetenskap, LiU

Föreläsning 13 Datastrukturer (DAT037)

Tentamen i Algoritmer & Datastrukturer i Java

Föreläsning 10 Innehåll. Prioritetsköer och heapar. ADT Prioritetskö. Interface för Prioritetskö. Exempel på vad du ska kunna

Några svar till TDDC70/91 Datastrukturer och algoritmer

Tentamen: Programutveckling ht 2015

Prov i DAT 312: Algoritmer och datastrukturer för systemvetare

Länkade listor kan ingå som en del av språket, dock ej i C Länkade listor är ett alternativ till:

Föreläsning 3: Dekomposition. Dekomposition

TDDC30. Objektorienterad programmering i Java, datastrukturer och algoritmer. Föreläsning 5 Jonas Lindgren, Institutionen för Datavetenskap, LiU

Föreläsning 5: Dynamisk programmering

Föreläsning 13. Dynamisk programmering

Ekvivalensrelationer

Lägg uppgifterna i ordning. Skriv uppgiftsnummer och din anmälningskod överst i högra hörnet på alla papper.

Föreläsning 1. Introduktion. Vad är en algoritm?

Tentamen Datastrukturer (DAT036)

Tentamen Datastrukturer (DAT037)

if (n==null) { return null; } else { return new Node(n.data, copy(n.next));

Informationsteknologi Tom Smedsaas 19 augusti 2016

Föreläsning 9 Exempel. Intervallhalveringsmetoden. Intervallhalveringsmetoden... Intervallhalveringsmetoden...

Föreläsning 9 Exempel

Rekursion. Koffman & Wolfgang kapitel 5

String [] argv. Dagens Agenda. Mer om arrayer. Mer om arrayer forts. String [] argv. argv är variabelnamnet. Arrayer och Strängar fortsättning

TDDC30. Objektorienterad programmering i Java, datastrukturer och algoritmer. Föreläsning 9 Jonas Lindgren, Institutionen för Datavetenskap, LiU

Föreläsning 5: Grafer Del 1

Programmering för språkteknologer II. OH-serie: Sökning och sortering. Algoritm

Dugga Datastrukturer (DAT036)

Tentamen Datastrukturer (DAT036)

Föreläsning 13 Datastrukturer (DAT037)

KTH, NADA, Vahid Mosavat. 1. Flervalsfrågor (5p)

Transkript:

Informationsteknologi Malin Källén, Tom Smedsaas 1 september 2016 Algoritmanalys Inledning Exempel 1: x n När vi talade om rekursion presenterade vi två olika sätt att beräkna x n, ett iterativt: x n = x x x x... x (1) och ett rekursivt: x n = { 1 om n = 0 xx n 1 om n > 0 (2) Båda dessa algoritmer genererar n multiplikationer. Exempelvis så kommer anropet power(x, 1000) att generera 1000 multiplikationer oavsett om power är implementerad enligt denition 1 eller denition 2. En annan möjlig denition är: 1 om n = 0 x n = (x n/2 ) 2 om n > 0 och n jämn (3) x(x n/2 ) 2 om n > 0 och n udda Notera att n/2 betecknar heltalsdivision! Denna denition är rekursiv, precis som denition 2, men i stället för att återföra lösningen av ett problem av storleken n på ett problem av storleken n 1 så använder vi oss av problem av storleken n/2, d.v.s. ett betydligt mindre problem. Javakod: public static double power(double x, int n) { if (n == 0) return 1.; else { double p = power(x, n/2); if (n % 2 == 0) //n jämn return p*p; else //n udda return x*p*p; Anropet power(x,1000) kommer att generera följande sekvens av anrop: power(x,1000) -> power(x,500) -> power(x,250) -> power(x,125) -> power(x,62) -> power(x,31) -> power(x,15) -> power(x,7) -> power(x,3) -> power(x,1) -> power(x,0) 1

Varje uppväckning av power utom den sista innehåller en eller två multiplikationer (kvadrering och eventuell multiplikation med x). Sammanlagt krävs endast 15 multiplikationer för att beräkna resultatet! Övning 1: Modiera programmet så att det även hanterar negativa exponenter. Tänk rekursivt! Hur många anrop kommer ett anrop power(x, -1000) att generera? Asymptotisk notation Om vi beräknar x n enligt denition 1 eller 2, kommer vi att behöva utföra n multiplikationer. Om vi däremot använder denition 3, kommer vi bara att behöva utföra log 2 (n) multiplikationer, förutsatt att n är en jämn tvåpotens. Om n inte är en jämn tvåpotens, krävs högst en extra multiplikation vid varje halvering av problemet, d.v.s. totalt 2 log 2 (n) multiplikationer. Med de första två denitionerna växer antalet multiplikationer linjärt med n. Man brukar säga att algoritmerna är O(n) eller att (tids)komplexiteten är n. O(n) uttalas ordo n. Med denition 3 växer antalet multiplikationer med log 2 (n). Man säger att denna algoritm är O(log(n)), eller att (tids)komplexiteten är log(n). (I nästa avsnitt ska vi se varför man inte behöver ange vilken logaritm som avses.) Mer formellt O anger egentligen en övre gräns: En funktion t(n) är O(f(n)) om det existerar två konstanter c och n 0 sådana att: t(n) c f(n) n > n 0 (4) Om vi applicerar denna denition på exemplen med antalet multiplikationer ovan, betecknar t(n) antalet multiplikationer som behöver utföras. Använder vi någon av de två första denitionerna är c = 1 och f(n) = n. Med denition 3 är f(n) = log 2 (n) och c = 2. I samtliga fall är n 0 = 0. I de här exemplen används alltså O för att ange (en övre gräns för) hur många gånger en central operation (i det här fallet multiplikation) används. Notationen kan också användas för att ange en övre gräns för hur lång tid eller hur stort lagringsutrymme en algoritm behöver. Tidsåtgången för en algoritm växer normalt lika fort som antalet operationer, men värdet på c beror då även på sådant som vilket språk algoritmen är implementerad i och vilken dator implementationen körs på. Eftersom O betecknar en övre gräns, är en algoritm som är O(log(n)) också O(n), O(n 2 ), O(2 n ) och så vidare. Tecknet Ω används för att ange en undre gräns: En funktion t(n) är Ω(f(n)) om det existerar två konstanter c och n 0 sådana att: t(n) c f(n) n > n 0 (5) Om funktionen t(n) är både O(f(n)) och Ω(f(n)) sägs den vara Θ(f(n)). (Naturligtvis måste man använda olika konstanter c för den övre och den undre gränsen.) 2

Ofta säger man O när man egentligen menar Θ. Notera att det inte spelar någon roll vilken logaritm man använder i O-, Θ- och Ω-uttrycken, eftersom 1 log a (x) = log b (a) log b(x) och 1 log b (a) kan bakas in i c. Därför behöver man heller inte ange vilken logaritm som avses. Fler exempel I detta avsnitt analyserar vi några algoritmer som ni känner till sedan tidigare. Exempel 2: Indicering i array Antag att du har en array som du ska hämta första elementet (eller något annat givet element) ur. Detta är ett exempel på när tidsåtgången inte växer med problemstorleken. Oavsett hur lång arrayen är kommer det att krävas lika många operationer för att hämta elementet. Indicering i array är alltså Θ(1) eller O(1) vilket är samma sak. Exempel 3: Sökning i osorterad array När det gäller sökning brukar man vid algoritmanalys skilja på lyckad och misslyckad sökning. Om värdet vi letar efter inte nns i arrayen, säger vi att sökningen misslyckas. En misslyckad sökning kräver att vi kontrollerar varje element i arrayen. (Hur ska vi annars kunna vara säkra på att värdet inte nns?) Antalet operationer växer alltså linjärt med problemstorleken (d.v.s. arrayens längd) och vi säger att en misslyckad sökning i en osorterad array är Θ(n). Om det eftersökta värdet nns i arrayen, säger vi att sökningen lyckas. Här är det relevant att skilja på bästa fall, värsta fall och genomsnitt. I värsta fall behöver vi leta igenom hela arrayen innan vi hittar det eftersökta värdet. I genomsnitt behöver vi leta igenom halva arrayen för att hitta det eftersökta värdet. I båda dessa fall växer antalet operationer linjärt med problemstorleken. I bästa fall hittar vi det eftersökta värdet på det första stället vi letar på. Då behövs ett konstant antal operationer -det spelar ingen roll hur lång arrayen är. En lyckad sökning i en osorterad array är alltså: Θ(1) i bästa fall Θ(n) i genomsnitt Θ(n) i värsta fall Generellt är man intresserad av en algoritms komplexitet i värsta fall och/eller i genomsnitt. Exempel 4: Sökning i sorterad array Om arrayen som vi ska söka i är sorterad, minskar mängden arbete avsevärt. I detta exempel kallar vi arrayen för a och det eftersökta värdet för v. Vi antar att elementen i a är sorterade i stigande ordning, d.v.s. så att a[i+1] a[i] för alla index i. En sökning går då till såhär: 1. Om arrayen är tom har sökningen misslyckats. v kan ju omöjligt nnas i en tom array. 3

2. Om a[n/2] == v är vi klara. 3. Annars, upprepa proceduren, den här gången med en hälften så lång array: Om a[n/2] > v, ge a[n/2+1:n] (d.v.s. den delen av a som ligger till höger om det n/2:te elementet) som input. Om a[n/2] < v, ge a[0:n/2-1] (d.v.s. den delen av a som ligger till vänster om det n/2:te elementet) som input. På det här sättet halverar vi problemstorleken i varje steg. Jämför med hur du själv skulle leta i en mängd sorterade element, exempelvis en telefonkatalog, en klasslista eller registret i en bok! Vid en misslyckad sökning behöver vi fortsätta tills arrayen är tom. Om vi skulle dubblera a:s längd, skulle antalet nödvändiga operationer öka med 1. Med andra ord växer mängden arbete logaritmiskt; algoritmen är Θ(log(n)). Vid en lyckad sökning behöver vi utföra samma procedur, men kommer i genomsnitt att hitta v efter halva tiden. Mängden arbete växer alltså som 0, 5 log(n), men 0, 5 är bara en konstant och kan bakas in i c. Algoritmen är alltså Θ(log(n)) i genomsnitt. Övning 2: Hur många operationer kräver en lyckad sökning i en sorterad array i bästa respektive värsta fall? Exempel 5: Fibonaccitalen När vi arbetade med rekursion, visade vi en enkel (men inte särskilt eektiv) metod för att beräkna det n:te Fibonaccitalet: 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); Tiden t(n) det tar att exekvera denna metod växer exponentiellt med n. Mer exakt så är t(n) c 1.618 n där c är en konstant som beror på dator, programmeringsspråk och så vidare. Bevis* För att inse det kan vi räkna hur många additioner anropet fib(n) utför. För detta antal t(n) gäller: { 0 om n 1, t(n) = 1 + t(n 1) + t(n 2) om n > 1. Observera likheten med Fibonaccitalen! 4

Detta är en linjär dierensekvation och den homogena ekvationen (som är lika med Fibonaccitalen!) har den karaktäristiska ekvationen som har lösningen r 2 r 1 = 0, r 1,2 = 1 ± 5, 2 d.v.s. den homogena ekvationen har lösningen F (n) = ar n 1 + br n 2, där a och b bestämmes ur begynnelsevillkoren. Eftersom t(n) = 1 är en partikulärlösning, kan den allmänna lösningen skrivas t(n) = ar n 1 + br n 2 1. Med hjälp av begynnelsevillkoren kan a och b bestämmas och ger lösningen a =(1 r 2 )/(r 1 r 2 ) b = (1 r 1 )/(r 1 r 2 ) Eftersom r 1 1.618 och r 2 0.618 < 1 så ser man att, för stora n, gäller t(n) 1.618 n Antalet additioner växer således exponentiellt och därmed växer också tiden t(n) exponentiellt. QED Övning 3: Tag reda på hur lång tid anropen fib(50) respektive fib(100) tar (skulle ta) på din dator. Svara med lämpliga enheter! (Sekunder är inte en lämplig enhet om det tar era timmar. Timmar är inte en lämplig enhet om det tar era dagar eller år.) Programmet är trädrekursivt, det vill säga varje anrop resulterar i två nya anrop. Detta kan potentiellt ge orimliga exekveringstider. Om du, när vi arbetade med rekursionsmaterialet, testkörde växlingsprogrammet med stora värden på a och n, märkte du förmodligen att exekveringstiden växte väldigt snabbt. Även den algoritmen är trädrekursiv och dess tidsåtgång växer exponentiellt. Med s.k. dynamisk programmering skulle växlingsproblemet kunna lösas eektivare, men det ligger utanför den här kursen. Som vi ser i avsnittet om sortering nedan, behöver trädrekursiva algoritmer inte nödvändigtvis ge orimliga exekveringstider. I själva verket är många klassiska eektiva algoritmer (t.ex. sorteringsalgoritmer och snabb Fouriertransform) trädrekursiva. Övning 4: I den här övningen vill vi att ni går tillbaka till SpecialStack, i vilken ni ck ändra och implementera några metoder när vi arbetade med rekursion. Tag genom mätning 5

och teoretiska beräkningar reda på hur lång tid detta program tar på din dator när stackens storlek är 10, 20, 30, 50 respektive 100. Svara med lämpliga enheter! (Sekunder är inte en lämplig enhet om det tar era timmar. Timmar är inte en lämplig enhet om det tar era dagar eller år.) Sortering Här presenterar och analyserar vi två rekursiva sorteringsalgoritmer. Instickssortering Den vanliga enkla instickssorteringen kan uttryckas rekursivt enligt följande: Induktionsantagande: Vi kan sortera n 1 element. Basfall: Vi kan sortera 1 element. Induktionssteg: Stoppa in det n:te elementet bland de n 1 redan sorterade elementen så att sorteringen bibehålls. Javakod: public static void sort(double [] a, int n) { if ( n > 1 ) { sort( a, n-1 ); // sortera de n-1 första double x = a[n-1]; int i = n-2; // flytta undan while ( i>=0 && a[i]>x ) { a[i+1] = a[i]; i--; a[i+1] = x; // lägg in sista Tiden att sortera n tal med denna algoritm beror på hur talen är permuterade. Det är enkelt att visa att tiden i genomsnitt är Θ(n 2 ). Övning 5: Hur lång tid tar instickssorteringen i bästa respektive värsta fall på sig för att sortera n element? Svaret ska ges på formen Θ(f(n)). Mergesort Nästa algoritm vi går igenom kallas för mergesort (eller samsortering på svenska). Den är, som analysen och övningen nedan visar, väsentligt eektivare än instickssorteringen. 6

Induktionsantagande: Vi kan sortera n/2 element. Induktionssteg: Dela mängden i två delar med vardera n/2 element, Sortera dessa var för sig och sammanfoga sedan de två sorterade delarna. Sortera n element 1. dela i två lika stora delar 2. sortera delarna var för sig 3. sammanfoga delarna Vi ger just nu ingen kod för denna sorteringsmetod. Det beror på att den är lite knölig att implementera och inte heller blir så bra för just arrayer. Vi återkommer till den senare för andra datastrukturer. Arbetet att sammanfoga de två sorterade delarna är proportionellt mot antalet element. (Uttrycket tiden är Θ(f(n)) kan, löst uttryckt, tolkas om att tiden är proportionell mot f(n) d.v.s. t(n) = c f(n).) Låt t(n) beteckna tiden att sortera n element. Då gäller { c om n = 0, t(n) = 2t(n/2) + dn om n > 0. 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 d.v.s. tiden är Θ(n log n). Övning 6: Antag att instickssortering och mergesort tar lika lång tid för 1000 tal -säg 1 sekund. Hur lång tid tar det för respektive algoritm att sortera 10 6 tal respektive 10 9 tal? Svara med lämpliga enheter! Praktiska tillämpningar Som vi har sett i ett par olika exempel ovan, kan tidsåtgången för algoritmer snabbt bli orimligt stor om man väljer fel algoritm (eller om det inte nns någon eektiv algoritm tillgänglig). Vi vill ha algoritmer för vilka tidsåtgången växer långsamt. Tiden för att lösa ett litet problem är ofta försumbar; det är när problemstorleken växer som det börjar bli viktigt att ha en eektiv algoritm. Som följande exempel visar, ska man dock inte stirra sig blind på komplexiteten. I praktiken 7

kan till exempel en algoritm som är Θ(n log(n)) vara minst lika eektiv som en algoritm som är Θ(n), om konstanten (c) är mycket större för den senare. Övning 7: Antag att du kan välja mellan två algoritmer, A och B, för att lösa ett problem. Vi låter n beteckna antalet element i den datastruktur som algoritmerna opererar på. Du vet att algoritm A löser ett problem av storlek n på n sekunder. Tidsåtgången för algoritm B är c n log(n) sekunder, där c är en konstant. Du testkör algoritm B på din dator och nner att den tar 1 sekund på sig för att lösa ett problem när n = 10. Hur stort måste n vara för att algoritm A ska ta kortare tid på sig än algoritm B? Det nns givetvis tillfällen då man faktiskt behöver lösa så här stora problem, men i många verkliga tillämpningar är antalet element förmodligen mindre. Det här var allt vi hade att säga om algoritmanalys för den här gången, men misströsta inte! När vi senare i kursen arbetar med listor och träd kommer ni att få er tillfällen att räkna på olika algoritmers komplexitet. 8