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



Relevanta dokument
Rekursion och induktion för algoritmkonstruktion

Tentamen OOP

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

6 Rekursion. 6.1 Rekursionens fyra principer. 6.2 Några vanliga användningsområden för rekursion. Problem löses genom:

4 13 / %.; 8 </" '': " / //&' " " ' * TelefonKostnad +,-%&. #!" $% " &' . > / ' 5 /' * 13/ &' static Math 1+" &'/ % 12 "" static "' * 1 /") %& &

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

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

Tentamen DE12, IMIT12, SYST12, ITEK11 (även öppen för övriga)

Föreläsning 6: Introduktion av listor

DELPROV 1 I DATAVETENSKAP

Objektorienterad programmering i Java

Föreläsning 13 och 14: Binära träd

Föreläsning 13. Rekursion

Fält av referenser. Konstruktorerna används för att skapa Bilar och Trafikljus.

Textsträngar från/till skärm eller fil

Metodanrop - primitiva typer. Föreläsning 4. Metodanrop - referenstyper. Metodanrop - primitiva typer

Grundläggande programmering, STS 1, VT Sven Sandberg. Föreläsning 12

Programmering A C# VT Ett kompendie över Programmering A (50p) i c# Stefan Fredriksson

Rekursion och induktion för algoritmkonstruktion

Grundläggande programmering med C# 7,5 högskolepoäng

Exempel på listor (klassen ArrayList). Ett exempel med fält. Avbildning är en speciell typ av lista HashMap.

Sätt att skriva ut binärträd

Objektorienterad programmering D2

Sökning och sortering

Tentamen i Objektorienterad programmering

OOP Objekt-orienterad programmering

Föreläsning 11: Rekursion

Tänk på följande: Det finns en referensbok (Java) hos tentavakten som du får gå fram och läsa men inte ta tillbaka till bänken.

1 Uppgift 1. a) Skapar ett Company-objekt med hjälp av den överlagrade konstruktorn. Du kan själv välja värden på instansvariablerna.

Lösningsförslag, tentamen FYTA11 Javaprogrammering

Föreläsning 1, vecka 7: Rekursion

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

Lösningar till tentauppgifterna sätts ut på kurssidan på nätet idag kl 19. Omtentamen i Programmering C, 5p, fristående, kväll,

Examination i. PROGRAMMERINGSTEKNIK F1/TM1 TIN212 (Dugga) Dag: Onsdag Datum: Tid: (OBS 3 tim) Rum: V

SMD 134 Objektorienterad programmering

Rekursion och induktion för algoritmkonstruktion

Lite om felhantering och Exceptions Mer om variabler och parametrar Fält (eng array) och klassen ArrayList.

Föreläsning 3-4 Innehåll

Grundläggande programmering, STS 1, VT Sven Sandberg. Föreläsning 11

Att använda pekare i. C-kod

Föreläsning REPETITION & EXTENTA

Rekursion: varför? Problem delas upp i mindre bitar algoritm för att lösa problemet erhålls från problemformuleringen

Föreläsning 2 Objektorienterad programmering DD1332. Typomvandling

Föreläsning 7. Träd och binära sökträd

Tentamen på kursen DA7351, Programmering , kl Malmö högskola Teknik och samhälle. DA7351, Programmering

Typkonvertering. Java versus C

Föreläsning 1 & 2 INTRODUKTION

732G Linköpings universitet 732G11. Johan Jernlås. Översikt. Repetition. Muddy. Funktioner / metoder. Punktnotation. Evalueringsordning

PROGRAMMERING-JAVA TENTAMINA

Övningar Dag 2 En första klass

Träd, binära träd och sökträd. Koffman & Wolfgang kapitel 6, avsnitt 1 4

Föreläsning 5&6 LOGISKA VARIABLER; IMPLEMENTERA KLASSER; MER ALGORITMER

Rekursion. Att tänka rekursivt Att programmera rekursivt i Java Exempel. Programmeringsmetodik -Java 254

Dugga Datastrukturer (DAT036)

Dagens föreläsning. Repetition. Repetition - Programmering i C. Repetition - Vad C består av. Repetition Ett första C-program

Programmeringsteknik med C och Matlab

Föreläsning 11 Datastrukturer (DAT037)

Tentamen för kursen Objektorienterad programvaruutveckling GU (DIT010)

Tentamen. 2D4135 vt 2005 Objektorienterad programmering, design och analys med Java Lördagen den 28 maj 2005 kl

Föreläsning 5. Rekursion

TENTAMEN PROGRAMMERINGSMETODIK MOMENT 2 - JAVA, 4P

Kungliga Tekniska Högskolan Ämneskod 2D4134 Nada Tentamensdag maj - 19 Tentamen i Objektorientering och Java Skrivtid 5 h

Dagens program. Programmeringsteknik och Matlab. Objektorienterad programmering. Vad är vitsen med att ha både metoder och data i objekten?

Föreläsning 2, vecka 8: Repetition

Tentamen, EDAA20/EDA501 Programmering

Vem är vem på kursen. Objektorienterad programvaruutveckling GU (DIT011) Kursbok Cay Horstmann: Big Java 3rd edition.

Att deklarera och att använda variabler. Föreläsning 10. Synlighetsregler (2) Synlighetsregler (1)

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

Uppgift 1 ( Betyg 3 uppgift )

TAIU07 Matematiska beräkningar med Matlab

2D1311 Programmeringsteknik för Bio1 och Bio2, vt 2003 Fiktivt prov På flervalsfrågorna är endast ett svar rätt om inget annat anges i frågan! Det rik

"if"-satsen. Inledande programmering med C# (1DV402)

Föreläsning 3-4 Innehåll. Diskutera. Metod. Programexempel med metod

Föreläsning 6: Metoder och fält (arrays)

Instuderingsfrågor, del D

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

TDDI16 Datastrukturer och algoritmer. Algoritmanalys

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

Tentamen i Grundläggande Programvaruutveckling, TDA548

Laboration 13, Arrayer och objekt

Chapter 3: Using Classes and Objects

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

Uppgift (poäng) 1 (2) 2 (3) 3 (4) 4 (4) 5 (3) 6 (4) 7 (6) 8 (6) 9 (8) Summa

Variabler som hör till enskilda objekt. Deklareras på översta nivån i klassen och i regel som private.

Tentamen TEN1 HI

Det finns en referensbok (Java) hos vakten som du får gå fram och läsa men inte ta tillbaka till bänken.

PROGRAMMERING-Java TENTAMINA

Programmering A. Johan Eliasson

Lösningsförslag till tentamen i EDA011/EDA017 Programmeringsteknik för F, E, I, π och N 27 maj 2008

Föreläsning 5 (6) Metoder. Metoder Deklarera. Metoder. Parametrar Returvärden Överlagring Konstruktorer Statiska metoder tostring() metoden javadoc

Dagens program. Programmeringsteknik och Matlab. Vad är arv? Vi ärver från GregorianCalendar. Kan vi bygga vidare på existerande klasser?

Föreläsning 8: Exempel och problemlösning

Idag. Javas datatyper, arrayer, referenssemantik. Arv, polymorfi, typregler, typkonvertering. Tänker inte säga nåt om det som är likadant som i C.

Det är principer och idéer som är viktiga. Skriv så att du övertygar examinatorn om att du har förstått dessa även om detaljer kan vara felaktiga.

Programmeringsteknik och Matlab. Dagens program. Viktiga datum. Repetitionsexempel. Repetition av if/else, for, while och Scanner

Metoder - en funktion: medel

Tentamen i Programmeringsteknik I

Det finns en referensbok (Java) hos tentavakten som du får gå fram och läsa men inte ta tillbaka till bänken.

Föreläsning 2. Täcker material från lektion 1, 2, 3 och 4:

Magnus Nielsen, IDA, Linköpings universitet

Transkript:

Algoritmanalys Analys av algoritmer används för att uppskatta effektivitet. Om vi t. ex. har n stycken tal lagrat i en array och vi vill linjärsöka i denna. Det betyder att vi måste leta i arrayen tills vi hittar vad vi vill eller tills arrayen tar slut. Genomsnittligen behövs n/2 jämförelser vilket är proportionellt mot n, vi säger att vi har en O(n) algoritm. Om vi å andra sidan har en sorterad array och binärsöker så får vi en O(log n) algoritm. Man brukar mäta detta i en proportionsalitetsfaktor som anger beroendet av antalet element man opererar på. 1 2 Vi kan sortera med en urvalssortering vilket betyder att vi går igenom alla talen n gånger, eftersom vi har n tal blir detta en O(n 2 ) algoritm. Om vi tänker en stund så ser vi att vi får väldigt olika faktorer beroende på algoritmen och tillväxten med antal element är väldigt olika: 10 100 1000 10000 1000000 log n 3 6 10 14 20 n 10 100 1000 10000 1000000 n log n 30 600 10000 140000 20000000 n 2 100 10000 1000000 10 8 10 12 Vi inser att gör stor skillnad hur algoritmen fungerar. Det kan vara oerhört viktigt att hitta effektiva algoritmer. Som exempel kan vi beräkna x^10 som x*x*x*x*x*x*x*x*x*x vilket uppenbarligen blir O(n) Men med lite tankearbete (eller litteraturstudier) ser vi att: x^10 är x^5 * x^5 x^5 är x * x^2 * x^2 x^2 är x * x Blir 1 + 2 + 1 pluttifikationer vilket summerar till 4. Vi inser att detta blir en O(log n) algoritm Kommer i princip att gå minst dubbelt så fort redan vid potensen 10. Nu har vi en del overhead och annat som kanske inte ger exakt det förhållandet men ökningen blir enligt denna formel. 3 4

Rekursiva algoritmer En rekursiv algoritm uttrycks i termer av sig själv. Exempel: n! kan beräknas med hjälp av följande sekventiella algoritm: p = 1; för i från 1 till n utför p = p * i förutsättning: p >= 0 Om vi formulerar samma sak rekursivt så blir det så här: n! = n * (n-1)! under samma förutsättning. t. ex. 5! = 5 * 4! eller utvecklat 5! = 5 * 4! = 5 * 4 * 3! = 5 * 4 * 3 * 2! = 5 * 4 * 3 * 2 * 1! = 5 * 4 * 3 * 2 * 1 * 0! = 5 * 4 * 3 * 2 * 1 * 0 * -1! =... Hmm något är fel här, algoritmen har inget slut! Det förefaller ju rätt självklart (jämför induktionsbevis) att man inte bara kan uttrycka en fakultet som en annan fakultet, man måste ju också ha ett slutvillkor. 5 6 Således n! = 1 om n = 0 eller 1 annars n! = n * (n-1)! En rekursiv algoritm måste ha: Ett icke rekursivt slutvillkor, ett basfall. En rekursiv formulering av det generella fallet. Räcker detta? f(n) = 0 om n = 0 annars f(n) = f(2*n) + n - 1 Alltså sammanfattningsvis: En korrekt rekursiv algoritm består av: Ett trivialt fall, kallat basfall, icke rekursivt. Ett generellt rekursivt fall som konvergerar mot basfallet, eller annorlunda uttryckt den generella formeln skall innebära en förenkling av uttrycket så att det så småningom blir lösbart. Tankegången med rekursion är att problemet är lite för svårt att lösa så vi uttrycker ett enklare problem ända till vi kan lösa det. Sedan går vi tillbaka och löser delproblemen. Blir detta speciellt bra? Nej eftersom det generella fallet aldrig konvergerar mot basfallet. 7 8

Ett enkelt exempel på en algoritm: Hur många personer är vi f. n. i detta rum? En icke rekursiv algoritm går då igenom raderna en i taget och ökar en räknare med ett för varje person som finns. En rekursiv algoritm formuleras så här: Om det bara är jag här så är det en person i rummet. Annars är det jag och alla andra vilket mer matematiskt uttryckt är 1 + antalet övriga personer. Illusteras praktiskt ganska enkelt. Ett annat exempel på detta är sortering. Vi kan utrycka sortering av en array på följande sätt: Om det vi har fler än ett element så leta upp det minsta talet i arrayen, swappa det mot det första elementet i arrayen. sortera sedan resten av vektorn (med första elementet borträknat) annars är vi klara. sort(v:array, start : heltal, antal :heltal) om antal fler än ett leta upp minsta värdet swappa det med v[start] sort(v, start + 1, antal - 1) annars klara eller i Java (utdrag ur nån klass) 9 10 public void sort(int [] arr, int start, int stop) // // urvalssortering, start = 1:a elementet att sortera // stop = sista elementet att sortera // if (start == stop) return; // bara ett element int min = start; for (int i = start; i <=stop; i++) if (arr[i] < arr[min]) min = i; int tmp = arr[min]; arr[min] = arr[start]; arr[start] = tmp; sort(arr,start+1,stop); // sortera resten public static void main(string [] args) int [] tal = new int[100]; Std.out.println( Ge 100 tal ); for (int i=0; i<100; i++) tal[i] = Std.in.readInt(); sort(tal, 0, 99); Std.out.println( Sorterad ); for (i=0; i<100; i++) Std.out.print(al[i] + ); if ((i + 1) % 10 == 0) Std.out.println(); Std.out.println(); Att notera: I) Rekursiva program blir ofta mer lättlästa och klara än sekventiella eftersom det försvinner en massa explicit repeterande. II) Det är ofta svårare att förstå ett rekursivt program. Vi har oftast inte en rekursiv hjärna. (det finns dom som har). III) Det blir inte alltid bättre eller effektivare med rekursion, ibland blir det katastrofalt ineffektivt ibland blir det mycket bra. En del problem är svåra att lösa sekventiellt men trivialt rekursivt och vice versa. 11 12

Som ett exempel på en ineffektiv rekursiv algoritm kan anföras Fibonci-serien, vilken som bekant beskriver förökningstakten hos kaniner givet att man har två av lämplig sort vid mätningens början. f(1) = f(2) = 1 f(n) = f(n-1) + f(n-2) om n>2 Vi ser ju att om vi vill ha det 7 Fibonci-talet så kommer vi att räkna ut det sjätte talet en gång medan t. ex. f(4) räknas ut tre gånger, f(3) fem gånger, f(2) åtta gånger o. s. v. Huru implementeras då rekursion i ett programmeringsspråk? Funktioner kan anropa sig själva! Det verkar ju lite inåtvänt men det måste ju alltid finnas någon utanför som gör ett initialt anrop. Hur blir det med parametrarna då? Sekventiellt blir det inga problem det blir en enkel repetition med n-2 varv, där varje tal beräknas en gång. 13 14 Kom ihåg följande: En lokal variabel är en variabel som deklareras i en funktion. De skapas när funktionen anropas. Vad händer då när den anropar sig själv? Tja hur ska funktionen veta vem som anropat den? Alltså skapas nya lokala variabler varje gång funktionen anropas oavsett vem som anropat. Värdeparametrar kopieras ju vid anrop. När en funktion anropar sig själv kopieras parametrarna innan de skickas till funktionen själv. Verkar lite vimsigt det här men det enklaste sättet att övertyga sin klentrogna hjärna är att vi för varje anrop får en ny funktion med egna lokala variabler och egna parametrar. Att alla funktionerna heter fak eller fib behöver vi inte bekymra oss om. Statiska variabler fungerar som vanligt, d. v. s. de delas mellan alla inblandade. Ex beräkna 5! med metoden public int fak(int n) if(n <= 1) return 1; else return n * fak(n - 1); public static void main(string [] args) Std.out.println(fak(5)); main anropar fak med parametern 5 fak anropar sig själv med parametern 4 fak anropar sig själv med parametern 3 fak anropar sig själv med parametern 2 fak anropas sig själv med parametern 1 fak returnar 1 till sig själv fak räknar ut 2*1 som den returnerar till sig själv fak räknar ut 2*3 som den returnerar till sig själv fak räknar ut 6*4 som den returnerar till sig själv fak räknar ur 24*5 som den returnerar till main 15 16

Några exempel på rekursiva enkla algoritmer: Läs ett antal tal och skriv ut dem i omvänd ordning sluta med en nolla. public void las() int tal; Std.out.print( Ge ett tal: ); tal = Std.in.readInt(); if (tal == 0) return; las(); Std.out.print(tal + ); om vi nu ger följande tal 4 5-7 6 12 3 4 5 7 0 dvs Ge ett tal: 4 Ge ett tal: 5 osv så skrivs 7 5 4 3 12 6-7 5 4 Hur kan det bli så? Vart tar talen vägen under tiden? 17 18 Proceduren har en lokal variabel med namnet tal där ett tal kan sparas. Algoritmen i övrigt är ju läs tal om tal skilt från 0 gör något skriv sedan ut tal om vi har 4 5-7 6 12 3 0 vurtar de på de häringa vise: main anropar las las läser in 4 till tal, anropar las las läser in 5 till tal, anropar las las läser in -7 till tal, anropar las las läser in 6 till tal, anropar las las läser in 12 till tal, anropar las las läser in 3 till tal, anropar las las läser in 0 till tal, återvänder sedan till las las skriver ut 3 återvänder till las las skriver ut 12 återvänder till las las skriver ut 6 återvänder till las las skriver ut -7 återvänder till las las skriver ut 5 återvänder till las las skriver ut 4 återvänder till main En funktion som skriver ut alla element i en array void skriv_vektor(int [] v[], int start, int antal) if(antal > 0) Std.out.print(v[start] + ); skriv_vektor(v, start+1, antal-1) Analys: Har vi ett trivialfall som inte är rekursivt? Svar ja, om antal = 0 Har vi ett rekursivt generellt fall som beskriver vad vi vill? Svar ja, Kommer det generella fallet att degenereras till trivialfallet? Svar ja, eftersom antal minskar för varje anrop Alltså en korrekt rekursion 19 20

Ett litet experiment: Antag att vi har följande lilla testprogram import java.util.*; public class PowTest public static double pow1(double x, int n) double p = 1; for (int i = 1; i <= n; i++) p = p*x; return p; public static double pow2(double x, int n) if(n == 1) return x; else return x * pow2(x, n-1); public static double pow3(double x, int n) if (n == 0) return 1; if (n == 1) return x; if (n % 2 == 0) return pow3(x*x, n/2); else return x*pow3(x*x, n/2); // ta tid på dessa public static void main(string [] args) Calendar dt1, dt2, dt3, dt4, dt5; double d1=0,d2=0,d3=0,d4=0; dt1 = Calendar.getInstance(); for (int i = 0; i < 10000; i++) d1 = Math.pow(5.0,195); dt2 = Calendar.getInstance(); for (int i = 0; i < 10000; i++) d2 = pow1(5.0,195); dt3 = Calendar.getInstance(); for (int i = 0; i < 10000; i++) d3 = pow2(5.0,195); dt4 = Calendar.getInstance(); for (int i = 0; i < 10000; i++) d4 = pow3(5.0,195); dt5 = Calendar.getInstance(); long t1,t2,t3,t4; t1 = dt2.gettime().gettime() - dt1.gettime().gettime(); t2 = dt3.gettime().gettime() - dt2.gettime().gettime(); t3 = dt4.gettime().gettime() - dt3.gettime().gettime(); t4 = dt5.gettime().gettime() - dt4.gettime().gettime(); System.out.println(t1 + " " + d1); System.out.println(t2 + " " + d2); System.out.println(t3 + " " + d3); System.out.println(t4 + " " + d4); > java PowTest 30 1.9913648889155653E136 104 1.991364888915566E136 928 1.991364888915566E136 37 1.991364888915566E136 Ger oss information om hur många millisekunder det tar att utföra 10000 anrop av var och en av de fyra funktionerna. Man kan notera att den inbyggda och den sista av våra är ungefär lika effektiva. Vår andra variant är långsammare och den rekursiva mycket långsammare. Kan man då dra slutsatsen att rekursiva algoritmer är sämre än icke rekursiva? Nej inte generellt. Ett metodanrop tar alltid en viss tid och om det mesta som sker i metoden är anrop så kommer en rekursiv att vara sämre. Om det som görs inuti metoden tar en större andel av tiden blir detta mindre viktigt. 21 22 Man kan göra följande lilla experiment: > java -Djava.compiler=none PowTest 52 1.9913648889155653E136 1777 1.991364888915566E136 5334 1.991364888915566E136 248 1.991364888915566E136 Vi ser att det blev ganska annorlunda. Nu har vi stängt av den inbyggda JIT en!!! Det gör att vi inte tjänar något på att utföra samma satser om och om igen. Det blir en ren tolkning av koden. Antag att vi har ett antal tecken i en följd. Skriv ut alla permutationer av denna teckenföljd Algoritm: om antalet tecken är 0, inga permutationer annars för i=1..n, lägg det i:e tecknet sist, skriv ut alla permutationer att av de n-1 tecknen. 23 24

public void swap(char [] s, int a, int b) char tmp = s[a]; s[a] = s[b]; s[b] = tmp; public void perm(char [] s, int n) // permutera n tecken //s[0], s[1],... s[n-1] perm(s,3); Ger oss utskriften > java PermTest bca a cab a b if (n==0) for (int i = 0; i < s.length; i++) Std.out.print(s[i]); Std.out.println(); else for (int i= 0; i<=n-1; i++) swap(s, i, n-1); perm(s, n-1); swap(s, i, n-1); public static void main(string [] args) char[] s = a, b, c ; 25 26 Fungerar så här utgå från: flytta a sist: flytta c sist: n == 0 ger: lägg tillbaka c: flytta b sist: n==0 ger lägg tillbaka b: lägg tillbaka a: lägg b sist: lägg a sist: n==0 ger oss lägg tillbaka a: flytta c sist: betrakta: n == 0 ger oss: lägg tillbaka c: lägg tillbaka b: flytta c sist: flytta a sist: n == 0 ger oss: lägg tillbaka a: lägg b sist: n==0 ger oss: klart. a bc b c a ca c a ab ba b ab ab a bca a cab a b 27