Algoritmer och Datastrukturer



Relevanta dokument
Algoritmer och Datastrukturer

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

Abstrakta datastrukturer

Algoritmer och Datastrukturer

UPPGIFT 1 V75 FIGUR 1.

Föreläsning 5. Rekursion

Föreläsning 5. Rekursion

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

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

Rekursion och induktion för algoritmkonstruktion

1, 2, 3, 4, 5, 6,...

UPPGIFT 1 TVÅPOTENSER. UPPGIFT 2 HISSEN I LUSTIGA HUSET.

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 4. Kö Implementerad med array Implementerad med länkad lista Djup kontra bredd Bredden först mha kö

Tentamen TEN1 HI

Föreläsning 6: Introduktion av listor

BINÄRA TRÄD. (X = pekarvärdet NULL): struct int_bt_node *pivot, *ny; X X X 12 X X 12 X X -3 X X

Tentamen TEN1 HI

Föreläsningsanteckningar, Introduktion till datavetenskap HT S4 Datastrukturer. Tobias Wrigstad

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

C++ Funktioner 1. int summa( int a, int b) //funktionshuvud { return a+b; //funktionskropp } Värmdö Gymnasium Programmering B ++ Datainstitutionen

Användarhandledning Version 1.2

Övningshäfte 2: Induktion och rekursion

Rekursion. Koffman & Wolfgang kapitel 5

Datastrukturer och algoritmer. Föreläsning 4 Test, Stack och Kö

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

Programmering i C++ En manual för kursen Datavetenskaplig introduktionskurs 5p

Programmera i C Varför programmera i C när det finns språk som Simula och Pascal??

Tentamen, Algoritmer och datastrukturer

Programmeringsteknik med C och Matlab

Skriv i mån av plats dina lösningar direkt i tentamen. Skriv ditt kodnummer längst upp på varje blad.

Tillämpad Programmering (ID1218) :00-13:00

TDIU01 - Programmering i C++, grundkurs

Föreläsning 13. Dynamisk programmering

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

Algoritmer, datastrukturer och komplexitet

Föreläsning 2 Datastrukturer (DAT037)

TDIU01 - Programmering i C++, grundkurs

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.

TENTAMEN I PROGRAMMERING. På tentamen ges graderade betyg:. 3:a 24 poäng, 4:a 36 poäng och 5:a 48 poäng

Tentamen Datastrukturer (DAT037)

DD1361 Programmeringsparadigm. Carina Edlund

Föreläsning 11. ADT:er och datastrukturer

Magnus Nielsen, IDA, Linköpings universitet

UPPGIFT 1 VÄNSKAPLIGA REKTANGLAR

Värmedistribution i plåt

Hitta k största bland n element. Föreläsning 13 Innehåll. Histogramproblemet

Fredag 10 juni 2016 kl 8 12

Algoritmer, datastrukturer och komplexitet

Tentamen *:58/ID100V Programmering i C Exempel 3

Sätt att skriva ut binärträd

Uppgift 1. Kylskåpstransporter

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

Uppgift 1 ( Betyg 3 uppgift )

Lär dig programmera! Prova på programmering med enkla exempel! Björn Regnell

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

Tentamen i. för D1 m fl, även distanskursen. lördag 26 februari 2011

DAI2 (TIDAL) + I2 (TKIEK)

Introduktion till programmering D0009E. Föreläsning 5: Fruktbara funktioner

Tentamen Datastrukturer för D2 DAT 035

Den räta linjens ekvation

HI1024, Programmering, grundkurs, 8hp KTH STH TENTAMEN. HI1024:TEN2 - Praktisk tentamen Tid: Fredagen den 21 oktober 2011,

Laboration 1. "kompilera"-ikonen "exekvera"-ikonen

Föreläsning 5 Innehåll

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

Välkommen till. Datastrukturer, algoritmer och programkonstruktion. eller DOA

Tentamen'('Datastrukturer,'algoritmer'och'programkonstruktion.'

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

Den räta linjens ekvation

HI1024 Programmering, grundkurs TEN

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

Datastrukturer och algoritmer

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

UPPGIFT 1 EURO. Utdata: Två rader, som för indata ovan, ser ut som följer: Före resan: bank 1 Efter resan: bank 3

Instruktioner - Datortentamen TDDD73 Funktionell och imperativ programmering i Python

Programmering II (ID1019) :00-17:00

HI1024 Programmering, grundkurs TEN

C++ Slumptalsfunktioner + switch-satsen

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

Tentamen Datastrukturer (DAT036)

Tentamen Datastrukturer (DAT036)

Objektorienterad programmering D2

HI1024 Programmering, grundkurs TEN

Iteration while-satsen

HI1024 Programmering, grundkurs TEN

MMA132: Laboration 2 Matriser i MATLAB

Datastrukturer, algoritmer och programkonstruktion (DVA104, HT 2014) Föreläsning 5

Föreläsning 3-4 Innehåll

Alla datorprogram har en sak gemensam; alla processerar indata för att producera något slags resultat, utdata.

Mathematica. Utdata är Mathematicas svar på dina kommandon. Här ser vi svaret på kommandot från. , x

TENTAMEN PROGRAMMERING I JAVA, 5P SOMMARUNIVERSITETET

Statistik över heltal

a = a a a a a a ± ± ± ±500

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

Övning från förra gången: readword

Datastrukturer och algoritmer

Introduktion till formella metoder Programmeringsmetodik 1. Inledning

Uppgift 1a (Aktiekurser utan poster)

Övning 2. (Länkade) Listor, noder

Transkript:

Kapitel 1 Algoritmer och Datastrukturer 1.1 Algoritm Vad är en algoritm? En bra algoritm är som en skarp kniv den gör exakt vad den är konstruerad för att göra, med minsta möjliga ansträngning. Att försöka lösa ett problem med en dålig algoritm, är som att försöka tranchera en stek med mejsel: Du kommer kanske att få ett ätbart resultat, men kommer att behöva lägga ner betydligt mer kraft än nödvändigt och resultatet kommer troligtvis inte att bli lika estetiskt tilltalande. En mer strikt definition Indata. Algoritmen tar emot indata Utdata. Algoritmen producerar ett resultat Precision. Varje steg är noggrant bestämt Determinism. Resultatet av varje steg är entydigt och bestäms endast av indata och resultatet från tidigare steg Ändlig. Algoritmen når sitt mål efter ett ändligt antal steg Korrekt. Algoritmen producerar ett korrekt resultat Generell. Algoritmen kan tillämpas på en given mängd indata 1.1.1 Ett enkelt exempel Givet: Tre tal a,b och c. Sökt: Det största av de tre talen 1

1.1. ALGORITM Algorithm 1.1.1: TRETALMAX(a,b,c) local x x a if b > x then x b if c > x then x c return (x) 1 Vi ser direkt att algoritmen TRETALMAX har tre tal, a,b,c, som indata och att dess utdata är ett tal x. Varje steg är klart definierat. Resultatet i varje steg är bestämt av indata och resultatet i tidigare steg. Algoritmen når sitt mål efter fyra steg. Vi tror att algoritmen är korrekt och vi förstår att den fungerar för alla uppsättningar av tre reella tal. Nu över till ett mer komplicerat problem 1.1.2 Sudoku 8 6 7 3 5 4 2 1 9 8 2 7 6 1 3 5 4 9 6 1 8 2 4 9 Figur 1.1: I figur 1.1 ser du sommarens problemfluga. I nästan alla dagstidningar finns, varje dag, en sudokuruta, tänkt att lösa endast med en penna som hjälpmedel, men här ska du använda datorn! Ett korrekt löst sudokuproblem har siffrorna 1...9 precis en gång i var och en av de 9 raderna, de 9 kolumnerna och de 9, 3 3 boxarna (begränsade av de något grövre strecken i figuren) 1 Vi kommer ibland att använda denna layout för att beskriva algoritmer Håkan Strömberg 2 KTH Syd

KAPITEL 1. REKURSION Skriv ett program som läser in problemet från filen sudoku.txt. Filen består av 9 rader med 9 tal ur mängden {0...9} på varje. 0 betecknar tom ruta. Programmet ska sedan ersätta alla 0:or med tal efter reglerna ovan och skriva ut lösningen Ett körningsexempel: 1 2 8 3 6 4 5 9 7 6 3 9 7 1 5 4 2 8 5 4 7 8 2 9 1 6 3 3 9 5 4 8 6 2 7 1 7 8 2 9 5 1 3 4 6 4 6 1 2 7 3 8 5 9 8 1 4 5 9 7 6 3 2 9 5 6 1 3 2 7 8 4 2 7 3 6 4 8 9 1 5 Ett riktigt sudokuproblem har endast en lösning, detta gäller även detta problem och våra testexempel. Just detta sudokuproblem är klassificerat som svårt, men när man använder en dator är alla problem lika lätta! eller? 1.1.3 Lösning Backtracking löser problemet med hjälp av följande algoritm. Sök upp första rutan som inte är ifylld. Testa med att fylla i rutan med talen 1...9 i tur och ordning. En aning knepigt är det att testa om ett tal finns i en viss box. Algorithm 1.1.2: SUDOKU(void) local fyllda local problem[9][9] LAESINDATA(problem, fyllda) SOLVE(problem, fyllda) Variabeln fyllda håller reda på hur många rutor som redan är ifyllda. Då senare, fyllda får värdet 81, är problemet löst. Håkan Strömberg 3 KTH Syd

1.1. ALGORITM Algorithm 1.1.3: SOLVE(problem, fyllda) local rad, kol, siffra if fyllda =81 then SKRIVUT(problem) for rad 1 to 9 for kol else { 1 to 9 do if problem[rad][kol] = 0 do then go to ut ut: for siffra 1 to 9 if TESTA(rad, kol, siffra, problem) problem[rad][kol] k do then SOLVE(problem, fyllda+1) problem[rad][kol] 0 Om inte 81 rutor är fyllda börjar vi leta efter första tomma rutan. När vi funnit den hoppar vi ut ur dubbelloopen. rad och kol pekar nu ut den tomma rutan, som försöker förses med alla talen 1...9. Allt beroende på funktionen tests resultat Algorithm 1.1.4: TESTA(rad, kol, siffra, problem) local r,k for r { 1 to 9 if problem[r][kol] = siffra do then return (0) for k { 1 to 9 if problem[rad][k] = siffra do then return (0) for r 3 (rad 1)/3 to 3 (rad 1)/3+2 for k { 3 (kol 1)/3 to 3 (kol 1)/3+2 do if problem[r][k] = siffra do then return (0) return (1) Här testas i tur och ordning aktuell kolumn, rad och box. Om inget av de 27 testade villkoren är sant returnerar vi 1 siffran kan placeras här. Om den verkligen ska vara i rutan kommer att visa sig senare. Håkan Strömberg 4 KTH Syd

KAPITEL 1. REKURSION 1 #include <stdio.h> 2 3 int testa(int rad,int kol,int tal,int tab[9][9]){ 4 int i,j; 5 for(i=0;i<9;i++) 6 if(tab[rad][i]==tal) 7 return 0; 8 for(i=0;i<9;i++) 9 if(tab[i][kol]==tal) 10 return 0; 11 for(i=rad/3 3;i<=rad/3 3+2;i++) 12 for(j=kol/3 3;j<=kol/3 3+2;j++) 13 if(tab[i][j]==tal){ 14 return 0; 15 } 16 return 1; 17 } 18 19 void solve(int n,int tab[9][9]){ 20 int i,j,k; 21 if(n==81) 22 skrivut(tab); 23 else{ 24 for(i=0;i<9;i++) 25 for(j=0;j<9;j++) 26 if(tab[i][j]==0) 27 goto ut; 28 ut: 29 for(k=1;k<=9;k++) 30 if(testa(i,j,k,tab)){ 31 tab[i][j]=k; 32 solve(n+1,tab); 33 tab[i][j]=0; 34 } 35 } 36 } 37 int main(void){ 38 FILE fil; 39 int i,j,n=0,tab[9][9]; 40 fil=fopen("sudoku3.txt","rt"); 41 for(i=0;i<9;i++) 42 for(j=0;j<9;j++){ 43 fscanf(fil,"%d",&tab[i][j]); 44 if(tab[i][j]!=0) 45 n++; 46 } 47 fclose(fil); 48 solve(n,tab); 49 } Håkan Strömberg 5 KTH Syd

1.1. ALGORITM Frågor man nu kan ställa sig om denna algoritm är Terminerar algoritmen? Producerar den korrekt resultat? Är den tillräckligt effektiv? Finns det en snabbare algoritm? Håkan Strömberg 6 KTH Syd

KAPITEL 1. REKURSION Rekursion 1.2 Inledning till rekursion Vi inleder kursen med begreppet rekursion, en programmeringsteknik som kommer att användas flitigt under kursen. Det är därför viktigt att du tillägnar dig denna teknik inför fortsättningen. Från Bra Böckers Lexikon klipper vi följande: Rekursiv: återkommande, som kan upprepas (bl.a. om sjukdomar); inom matematik och datateknik, något som implicit upprepas på ny nivå. Rekursivt formulerade lösningar är ofta eleganta och betraktas inte sällan som geniala. Det är lite av programmerarens adelsmärke, att kunna använda sig av rekursion. Likväl kan rekursion, för dig som ser det för första gången, te sig både svår och mystisk. Det rekursiva sättet att lösa problem, bygger på att reducera det ursprungliga problemet till ett antal delproblem, som har samma struktur som ursprungsproblemet men som är enklare att lösa. Resultatet från delproblemen kan sedan användas till lösandet av det ursprungliga problemet. 1.2.1 Ett första exempel Vårt första exempel får bli en rekursionsformel, en formel där man kan bestämma till exempel, ett tal i en talföljd med hjälp av föregående tal. Dessa formler är speciellt enkla att översätta till datorprogram. Fibonacci-följden är kanske den mest kända rekursionsformeln. Exemplet utgör en nära släkting Givet är följande formel: h n = h n 1 +h n 2 +1 och att h 1 = h 2 = 1 Antag att man vill bestämma h 4. Allt vi kan säga i första steget, är att h 4 = h 3 +h 2 +1, där vet vi att h 2 = 1. Därför återstår bara att bestämma h 3, som ju är h 3 = h 2 + h 1 + 1. h 4 kan nu skrivas h 4 = h 2 +h 1 +1+h 2 +1 = 1+1+1+1+1 = 5. Håkan Strömberg 7 KTH Syd

1.2. INLEDNING TILL REKURSION Vi förstår att vi kan använda denna teknik för att bestämma h n för vilket naturligt tal n som helst, bara vi har tillräckligt med tålamod. Återstår att visa hur en rekursiv funktion i C kan lösa detta problem. Indata är n för vilket vi vill bestämma h n. 1 int h(int n){ 2 if(n==1 n==2) 3 return 1; 4 else 5 return h(n 1)+h(n 2)+1; 6 } Funktionen h består av en if-sats. Om n = 1 eller n = 2 är svaret omedelbart klart det blir 1. I annat fall ska vi bestämma h n 1 + h n 2 + 1. Detta innebär att vi ska anropa funktionen h två gånger men med ett mindre n. Det ovanliga eller det nya (för oss) är att funktionen anropar sig själv, vilket kännetecknar rekursiva funktioner. Detta kan leda till nya anrop som i sin tur kan leda till ytterligare anrop och så vidare... Vi följer anropet h(4). Villkoret i if-satsen är inte sant, vilket leder till två nya anrop h(3) och h(2). Det första av dessa leder till två nya anrop h(2) och h(1). Båda dessa returnerar 1. Genom trädet i figur 12.1 ser vi hur funktionen till sist får värdet 5. Figur 1.2: Genom ett träd ser vi hur h(4) exekveras UPPGIFT 1.1 Några enkla frågor Några frågor kring det inledande exemplet. Hur många gånger anropas funktionen h(n) för n = 4 Vilket värde har h(5)? Hur många anrop av h(n) görs då h(5), ska beräknas? 1.2.2 Detta händer vid funktionsanrop Från tidigare känner vi till att varje gång en funktion anropas i C, så skapas utrymme på stacken där de lokala variablerna, formella parametrarna och återhoppsadressen lagras. När funktionen Håkan Strömberg 8 KTH Syd

KAPITEL 1. REKURSION exekverats, försvinner dessa uppgifter från stacken och hopp sker till den funktion från vilket anropet skedde. Figurerna nedan vill visa detta. Figur 1.3: Ett vanligt anrop av en funktion För att vara riktigt tydliga tar vi ytterligare ett icke rekursivt exempel. På stacken finns från början de variabler som är deklarerade i main. När anrop av funk1 görs från main allokeras alltså plats för de lokala variabler som finns i funk1 på stacken. Dessutom finns adressen till den plats i main varifrån anropet skedde, återhoppsadressen. Någonstans från funk1 görs nu ett anrop av funk2. På samma sätt bereds plats på stacken för lokala variabler i funk2 Samt för återhoppsadressen, till någonstans i funk1 När funk2 har exekverats klart fortsätter exekveringen i funk1. På den plats som pekas ut av återhoppsadressen. Det utrymme på stacken som upptogs av lokala variabler i funk2 under dess exekvering återlämnas nu. På samma sätt når så småningom funk1 sitt slut lokala variabler tas bort från stacken och återhopp sker till main. Figur 1.4: Huvudfunktionen anropar en funktion som i sin tur anropar en funktion I figur 12.4, ser vi så till sist ett rekursivt anrop och förstår att det inte är någon principiell skillnad mellan detta och förra exemplet. Då återhopp sker rensas allt från just det anropet bort från stacken och exekveringen fortsätter vid återhoppsadressen. 1.2.3 Avbrott Av detta och det inledande exemplet förstår vi hur viktigt det är att rekursionen har ett slut. Om det inte funnits en gren i if-satsen som sagt att då n = 1 eller n = 2, så är h = 1, ja då hade anropandet Håkan Strömberg 9 KTH Syd

1.3. NÅGRA EXEMPEL MED REKURSION Figur 1.5: Ett rekursivt anrop: Huvudfunktionen anropar en funktion som anropar sig själv av nästa nivå pågått i all evighet (i själva verket bryts exekveringen av att stacken blir full). Teoretiskt är det alltså möjligt att bestämma h(n) för vilket naturligt tal n som helst med hjälp av vår funktion, men i praktiken kommer inte stacken att rymma all information som behövs för stora värden på n. 1.2.4 Effektivitet Motsatsen till rekursion heter iteration att upprepa samma rutin flera gånger till exempel genom en while eller for-loop. Skillnaden mellan dessa tekniker är bland annat att man hos iterationen slipper att betala för den administration som krävs vid funktionsanrop. Detta talar för iteration. I övrigt kan man inte säga vilken teknik som är effektivast. Det hela beror på algoritmen som används. Det är en utbredd missuppfattning att rekursiva algoritmer är snabbare än iterativa. Det kan vara och är oftast precis tvärt om! Om vi åter tittar på vårt inledande exempel, så har vi alltså från början 1, 1... Nästa tal, det för n = 3 får vi genom att addera de två föregående och till detta lägga 1, alltså 3. Plötsligt blir det enkel huvudräkning för att få fram 1, 1, 3, 5, 9, 15, 25... Denna iterativa algoritm är betydligt effektivare. En annan aspekt på effektivitet är den tid och möda som krävs för att skriva koden. Det kan i vissa situationer gå snabbare att skriva programmet för en rekursiv algoritm än för en iterativ. Ofta blir detta kort och elegant. Men först måste man lära sig att tänka rekursivt och det är inte alltid så lätt. 1.3 Några exempel med rekursion 1.3.1 Fakultet Vi börjar med det mest traditionella av alla exemplen på rekursion. Funktionen fact bestämmer n! = 1 2 3... n. 1 int fact(int n){ 2 if (n==0) 3 return 1; 4 else 5 return n fact(n 1); 6 } En tabell får visa hur resultatet växer fram. När vi nått ner till avbrottet fact(0) är allt uppradat och det är bara att multiplicera. Håkan Strömberg 10 KTH Syd

KAPITEL 1. REKURSION fact(5) 5 fact(4) 5 4 fact(3) 5 4 3 fact(2) 5 4 3 2 fact(1) 5 4 3 2 1 fact(0) 5 4 3 2 1 1 120 Att funktionen inte bryter förrän vid n = 0 beror på att den ska fungera även för 0! = 1. Alternativt skulle vi kunna ha avslutat det hela genom denna sats och därmed tjänat in ett anrop if (n==0 n==1) return 1 Man ska nu inte ledas till att tro att detta är det enda sätt på vilket n! kan beräknas, inte heller till att det är det mest effektiva sättet. Programuppgiften skulle mycket väl ha kunnat förekomma i början av C-kursen och då sett ut som nedan. Det ger samma resultat (förstås), som programmet ovan och är något effektivare. 1 int fact1(int n){ 2 int i,p; 3 p=1; 4 for(i=1;i<=n;i++) 5 p =i; 6 return p; 7 } Algoritmerna (om man nu kan säga att det är två olika) är däremot jämförbara ur effektivitetssynpunkt. Lika många multiplikationer, additioner (subtraktioner) och jämförelser krävs för att bestämma n!. Om vi bortser (vilket vi kan göra) från rekursionens extra kostnad så kan vi säga att algoritmerna är helt likvärdiga. Skillnaden i effektivitet ligger i det vi nämnde ovan. För varje gång man anropar en funktion i C ska parametrar, lokala variabler och återhoppsadress lagras på stacken. När anropet är färdigexekverat ska motsvarande minne som dessa data tog upp friställas. Det är storleken på n som avgör hur mycket arbete datorn ska utföra. Ökar vi n med 1 ökar antalet anrop av den rekursiva funktionen med ett och antalet varv i den iterativa rutinen med ett. Mängden arbete växer linjärt med n. Den här typen av resonemang ska vi genomföra mer seriöst i kommande föreläsning. UPPGIFT 1.2 Största värdet. Både fact och fact1 har en och samma klara begränsning vilket är det största värde på n för vilka det fungerar med den kompilator du använder? 1.3.2 Fibonacci Den italienske matematikern Leonardo från Pisa eller Fibonacci, som han oftare kallas, betraktas som Europas förste matematiker. Han har bland annat givit sitt namn till talföljden1, 1, 2, 3, 5, 8, 13,... Håkan Strömberg 11 KTH Syd

1.3. NÅGRA EXEMPEL MED REKURSION definierad som f n = f n 1 +f n 2 där f 0 = 1 och f 1 = 1 Ett problem i kapitel tre av Fibonaccit s Liber abbaci ledde till introduktionen av denna talföljd: En man lät placera ett par kaniner i en inhägnad trädgård. Hur många par av kaniner kan under ett år produceras, med utgång från detta par, om vi antar att varje par nedkommer med ett nytt par varje månad från och med att paret uppnått två månaders ålder? Första och andra månaden finns bara det ursprungliga paret, 1,1. Månad tre och fyra föds ett nytt par. Vi har nu 1,1,2,3. Den femte månaden föds två par och månad sex tre par... Eftersom alla kaninerna överlever kommer det att finnas f 11 = 144 par med kaniner efter ett år Funktionen nedan tar emot ett ordningsnummer n och bestämmer det n:te fibonaccitalet. 1 int fib(int n){ 2 if (n==0 n==1) 3 return 1; 4 else 5 return fib(n 1)+fib(n 2); 6 } En iterativ metod. Med samma teknik som för vårt inledande exempel kan vi här använda en effektivare algoritm. Algorithm 1.3.1: FIB2(n) local f 0 = 1,f 1 = 1,f 2 for I 2 to n f 2 f 0 +f 1 do f 0 f 1 f 1 f 2 return (f 1 ) 2 Loopen kommer inte ens att exekveras n varv för att bestämma f n. Ökar vi n med 1 kommer loopen att exekveras en extra gång. Arbetet växer linjärt med n. Hur är det då med vår första funktion fib hur många gånger kommer den att exekveras för talet f n? Ett sätt att få reda på det utan att egentligen anstränga sig är att placera en räknare (globalt 2 Vi inför här ett speciellt språk för att uttrycka algoritmer. Tanken är att språket ska vara lätt att förstå och friare, samtidigt som vi ska slippa se onödiga detaljer som motsvarande C-program skulle innehålla. Normalt kommer det att bli ganska enkelt att översätta algoritmen till ett fungerande program. Håkan Strömberg 12 KTH Syd

KAPITEL 1. REKURSION deklarerad) i funktionen. För f 5 = 8 anropas funktionen 15 gånger och för f 13 = 377753 gånger. Av detta sluter vi oss till att funktionen för f n anropas 2 f n 1. Den här algoritmen tvingar datorn att utföra ett betydligt större arbete än dess iterativa kusin. Algoritmen fib arbetar linjärt med f n. UPPGIFT 1.3 Skillnad mellan talföljder Vilken är skillnaden mellan Fibonaccis talföljd och den vi definierade i det inledande exemplet? Om man känner f n vad blir då h n? En explicit formel. Det är inte slut här heller. Med hjälp av formeln f n = ( 1+ ) (n+1) 5 1 ( ) (n+1) 5 2 2 5 kan man bestämma f n i ett enda slag. Effektivare än så här kan det inte göras. Tidsåtgången är konstant, oberoende av storleken på n. Här har vi sett tre olika sätt att bestämma fibonaccitalet f n. Med början från det mest resurskrävande till det effektivaste Vi återkommer till Fibonacci och hans talföljd i samband med dynamisk programmering lite längre fram i kursen. 1.3.3 Gissa mitt tal Vi återknyter till en programmeringsuppgift som förekom i grundkursen. Vi önskar ett program, där datorn tänker på ett heltal mellan 1 och 100. Programmet ska sedan själv (!) gissa det hemliga talet (utan att fuska), till skillnad från tidigare version där du skulle gissa. Men precis som tidigare finns tre svar på gissningen RÄTT, FÖR STORT, FÖR LITET. Programmet fortsätter att gissa tills den får rätt svar. När problemet gavs kom de flesta på följande algoritm: Algorithm 1.3.2: GISSA(min, max, antal) local x x (min+max)/2 if x = hemlig then return (antal) else if x < hemlig then GISSA(x+1, max, antal+1) else GISSA(min,x 1, antal+1) Alla gissningar görs med ett tal så nära mitten som möjligt i det möjliga intervallet. Låter vi denna funktion verka på alla tal 1...100 så kommer det att i genomsnitt behövas 4.8 gissningar. Utökar vi intervallet till att omfatta 1...1000 blir medelvärdet 7.99. Vad vi visat här är inget annat än vad vi senare ska studera under rubriken binärsökning. Håkan Strömberg 13 KTH Syd

1.3. NÅGRA EXEMPEL MED REKURSION Den här algoritmen kan förstås mycket enkelt uttryckas iterativt. Men nu är det ju i första hand rekursion som gäller. 1.3.4 Att skriva ut ett tal För att visa att nästan allt går att utföra rekursivt visar vi här en funktion som skriver ut ett tal n på bildskärmen. 1 void printnum(int n){ 2 if(n<10) 3 printf("%d",n); 4 else { 5 printnum(n/10); 6 printf("%d",n%10); 7 } 8 } Anropar vi funktionen genom printnum(1234), så kommer den att bryta ned talet n tills det blir < 10 och då skrivs detta n ut. Men vad händer sedan, eller rättare sagt vad har hänt innan? Eftersom det i 5 är fråga om en heltalsdivision 1234/10 = 123 kommer printnum att anropas nästa gång genom printnum(123) Därefter sker anrop i tur och ordning med printnum(12) och printnum(1). Genom det sista anropet skrivs talet 1 ut. Efter återhoppet är vi tillbaka till det anrop då n = 12 och sista satsen i funktionen skriver ut 2. Återhopp sker nu till den plats då n = 123 och 123 mod 10 = 3, som skrivs ut. Därefter kommer vi tillbaka till det första anropet. Det då n = 1234 och eftersom 1234 mod 10 = 4 så blir det 4 som till sist skrivs ut. Figur 1.6: Vi ska ta oss från övre högra hörnet till det nedre vänstra. Ett av de möjliga vägarna är markerad Håkan Strömberg 14 KTH Syd

KAPITEL 1. REKURSION 1.3.5 Hur många vägar? I figur 8.10 ser vi ett nätverk av gator. Vi tänker oss att vi startar längst upp i högra hörnet och vill ta oss ned till vänstra hörnet. Vi får bara gå åt väster eller söder, men kan byta riktning i varje gatukorsning. Frågan är nu hur många olika promenader vi kan göra. I figur 8.10 ser du en av flera inritad. Algorithm 1.3.3: ANTALVÄGAR(m, n) local v = 0 if m = 0 and n = 0 then return (1) if m > 0 then v v+antalvägar(m 1,n) else if n > 0 then v v+antalvägar(m,n 1) return (v) Indata är kartans höjd (m) och kartans bredd, (n) i kvarter räknat. Vi startar i (m,n) och är på väg till (0,0). Varje gång vi når målet har vi hittat en ny väg och returnerar 1. Antalet funna vägar v ackumuleras till det sökta antalet. Figur 1.7: Genom detta träd kan man följa exekveringen av ANTALVÄGAR(2,3) Anropet ANTALVÄGAR(2,3) kan åskådliggöras med ett träd som i figur 8.11. I varje nod ger vi koordinaterna för gatukorsningen. Att bevisa att antalet vägar är ( ) m+n n överlämnar vi till matematiken. Även här har vi alltså skapat en algoritm som söker ett resultat på ett mer omständligt sätt än den explicita formel som matematiken ger oss. Nu är det inte alltid så matematiken kan inte alltid ge så stor hjälp det kommer vi att se längre fram i kursen. UPPGIFT 1.4 Vilka vägar? Utgå från kartan i figuren och svara på följande frågor. Håkan Strömberg 15 KTH Syd

1.3. NÅGRA EXEMPEL MED REKURSION Vilken är den första vägen algoritmen hittar? Vilken är den sista vägen algoritmen hittar? Vilken är den näst sista vägen algoritmen hittar? Hur ska algoritmen ändras för att vägarna ska hittas i omvänd ordning? UPPGIFT 1.5 Hur många anrop? Implementera algoritmen i C och tar reda på hur många anrop den gör för olika värden på m och n 1.3.6 Euklides algoritm Euklides (Euclid of Alexandria) är den mest prominenta av antikens matematiker. Han är känd för sin matematiska avhandling, Elementa, som näst Bibeln är den mest spridda skriften i Västerlandet. Eftersom hans böcker användes långt in på 1900-talet, så måste han vara alla tiders största matemtiklärare. Han har bland annat givit namn åt en algoritm för att bestämma största gemensamma delaren till heltal (SGD(a,b), engelska gcd(a,b)). Denna algoritm kallas följaktligen Euklides algoritm och ger till exempel SGD(78, 21) = 3 eller att SGD(102,31) = 1. I SGD(a,b) = c, där a b > 0, är c det största heltal som delar både a och b, a mod c = 0 och b mod c = 0, vilket inte är sant för något annat heltal > c. Nedan följer ett exempel på hur algoritmen fungerar för att ta reda på största gemensamma delaren till SGD(78, 21) 78 = 3 21 + 15 21 = 1 15 + 6 15 = 2 6 + 3 6 = 2 3 + 0 21 går i 78, 3 gånger med resten 15. 15 går i 21, 1 gång med resten 6. 6 går i 15, 2 gånger med resten 3 3 går i 6, 2 gånger, resten blir 0, och beräkningarna avbryts. Den största gemensamma delaren är den sista resten som inte är 0 alltså 3. Vi beskriver algoritmen rekursivt (och väljer namnet GCD, greatest common divisor) på följande sätt: Algorithm 1.3.4: GCD1(a, b) if b = 0 then return (a) else return (GCD1(b, a mod b)) Håkan Strömberg 16 KTH Syd

KAPITEL 1. REKURSION Den ser förföriskt enkel ut, speciellt om vi jämför den med den iterativa implementationen. Algorithm 1.3.5: GCD2(a, b) local r = b,q while r > 0 q a/b r a q b if r = 0 do then{ return (b) a b else b r y = x (engelska floor), y största heltal x. a/b är detsamma som heltalsdivision i C, så länge a/b 0. Algoritmen GCD3 är den man kanske själv skulle komma på. Hur står den sig i konkurrensen? Algorithm 1.3.6: GCD3(a, b) local i for i { b downto 1 if b mod i = 0 and a mod i = 0 do then return (i) Man kan inte säga något definitivt om det arbete som behöver utföras efter anropet GCD3(a, b). För GCD3(120,40) = 40 når algoritmen sitt mål omedelbart, men efter GCD3(171,170) = 1 utför den 170 varv med 340 divisioner. Det är alltså inte storleken hos a och b som bestämmer arbetsåtgången, utan snarare dessa tals inbördes förhållande. Vi kan fastslå en övre gräns för GCD3, som motsvarar 2 b divisioner. Inte vid något tillfälle kräver GCD1 eller GCD2 mer arbete än GCD3. Den övre gränsen för dessa algoritmers arbete är svårare att bestämma och vi återkommer därför till detta i en senare föreläsning. Observera att vi här har tittat på två olika algoritmer. Den första, Euklides, har implementerats på två olika sätt. UPPGIFT 1.6 Euler s funktion. Om SGD(a,b) = 1, säger vi att a och b är relativt prima. För Euler s φ-funktion är φ(n) antalet tal 1... n som är relativt prima med n. Skriv ett program som bestämmer φ(n) för önskat n. Håkan Strömberg 17 KTH Syd

1.4. PROGRAMMERINGSUPPGIFTER 1.4 Programmeringsuppgifter UPPGIFT 1.7 Siffersumman. Om t = 1765 så är ss = 19 och om t = 128956 så är ss = 31, eller hur? Efter en stunds funderande hade du förstås kommit på att ss är siffersumman till t (summan av de i talet ingående siffrorna). Skriv nu en rekursiv funktion SIFFERSUMMA(T) som bestämmer ss för ett heltal t > 0. UPPGIFT 1.8 Binomialkoefficienten Så här ser toppen av Pascals triangel ut 1 1 1 1 2 1 1 3 3 1 1 4 6 4 1 1 5 10 10 5 1 Du kommer säkert ihåg hur man bildar nästa rad i triangeln. Om inte, så har du svaret här ( ) ( ) ( ) n+1 n n = + m m m 1 Det vill säga, man får ett nytt tal med ordningsnummer m på rad n + 1 genom att addera de två tal som står snett ovanför, till höger respektive vänster. Båda på rad n med ordningsnumren m och m 1. Varje rad inleds och avslutas med talet 1 Du ska nu skriva en rekursiv funktion binomkoeff(n,m) som beräknar binomialkoefficienten för givna n och m. Det är bra att känna till att ( ) ( ) ( ) n n n = = 1 = n 0 n 1 Vad blir ( 13 4) och hur många gånger anropar funktionen sig själv för att beräkna detta tal? UPPGIFT 1.9 Myntmaskin En maskin har en display på vilken den visar uppnådd poäng. Från början visar displayen 1 poäng. Genom att stoppa mynt i maskinen kan poängen förändras. Genom en 10-öring multipliceras poängen på displayen med 3 Genom en 5-öring adderas 4 till poängen på displayen. Skriv ett program som tar emot den poäng som ska uppnås. Programmet ska sedan beräkna och skriva ut det lägsta belopp som krävs för att uppnå poängen. Observera att man inte kan nå målet för alla slutpoäng. En testkörning Vilken poäng ska uppnås: 109 Poängen kan nås med 45 öre Håkan Strömberg 18 KTH Syd

KAPITEL 1. REKURSION UPPGIFT 1.10 MergeSort. Antalet jämförelser i sorteringsalgoritmen MERGESORT kan beräknas med hjälp av följande rekursiva funktion f(n) = f( n/2 )+f( n/2 )+n 1 där f(1) = 0. a/b står för det minsta heltal c sådant att c a/b. a/b står för det största heltal c sådant att c a/b. Skriv ett program som för inmatat n bestämmer f(n). Hur många tal ska sorteras? 1000 För detta krävs 8977 jämförelser UPPGIFT 1.11 Samma summa. Följande rekursiva formel kan användas för att bestämma på hur många sätt talet n kan bildas som en summan av positiva heltal. part(n,m) = 1 n = 1 och m 0 eller m = 0 eller m = 1 0 m < 0 part(n 1,m)+part(n,m n) annars Testexempel part(5, 5) = 7, 5 = 4+1 = 3+2 = 3+1+1 = 2+2+1 = 2+1+1+1 = 1+1+1+1+1 UPPGIFT 1.12 Kvadratroten. Med hjälp av följande rekursiva formel kan man bestämma roten ur 1 n 300. Resultatet levereras i a, som ursprungligen har värdet a = 1. Talet 0.0000001 e 0.001 anger med vilken noggrannhet resultatet ska beräknas a a 2 n < e ROT(n,a,e) = ( ) ROT n, a2 +n 2a,e annars Testexempel ROT(2, 1, 0.001) = 1.41421. e garanterar att tre decimaler är korrekta. UPPGIFT 1.13 Bitstring. Vi önskar här en rekursiv funktion, BitStr, som skriver ut alla 2 n binärsträngarna med den givna längden n. Funktionen anropas inledningsvis med till exempel BitStr("",0,3). Det betyder att vi i utskriften vill ha 000,001,010,011,100,101,110,111, helst i den ordningen. Man kan till exempel använda strcat, med vars hjälp man kan slå samman två strängar. Tips: Någonstans i funktionen kan följande kod finnas BitStr(strcat(s,"0"),k+1,3), som alltså betyder att man lägger till en 0:a sist i strängen. Med k+1 noteras antalet tecken i strängen. Håkan Strömberg 19 KTH Syd

1.4. PROGRAMMERINGSUPPGIFTER UPPGIFT 1.14 Roboten. Med hjälp av en robot vill man sortera de fem paketen, som ligger på hyllan i figur 1.8, Figur 1.8: så att de ligger i bokstavsordning, från vänster till höger. Roboten klarar två olika funktioner. b Byta plats på de första två paketen, de längst till vänster s Flytta det sista paketet längst fram, paketet som ligger längst till höger, läggs längst till vänster. Genom dragen bsssbsb förvandlas ordningen BECAD till ABCDE. Skriv ett program som tar emot en sträng med de fem bokstäverna ABCDE i godtycklig ordning och som sedan bestämmer det minsta antalet steg roboten behöver utföra, för att ordningen ABCDE ska uppnås. Ett körningsexempel: Hur ligger paketen? BECAD Det krävs 7 steg UPPGIFT 1.15 Hissen i lustiga huset. I Lustiga Huset finns v,1 v 100 våningar. I den märkliga hissen finns bara två knappar. Dels en som förflyttar hissen u våningar uppåt och dels en som förflyttar hissen n våningar nedåt. Men med hjälp av en kombination av resor, uppåt och nedåt kan man ta sig till önskad våning m. Du ska skriva ett program som tar emot uppgifter om v,u,n och m och som sedan beräknar det minsta antalet resor som behövs för att nå våning m, målet. En resa är alltså en knapptryckning som för hissen från en våning till en annan. Den första resan startar alltid på våning 1, som ligger i husets bottenplan. Huset saknar källare och vind, vilket betyder att hissen alltid måste befinna sig någonstans mellan våning 1 och v. Indata: Programmet inleds med att fråga efter v,u,n och m Hur många våningar har huset: 78 Förflyttning uppåt: 15 Förflyttning nedåt: 8 Till vilken våning ska du: 35 Utdata: En rad som talar om det minsta antalet resor som behövs för att nå målet: Det behövs minst 13 resor för att nå målet Endast testexempel där det finns en lösning kommer att användas. Håkan Strömberg 20 KTH Syd

KAPITEL 1. REKURSION UPPGIFT 1.16 Plankan. Figur 1.9: Man vill skapa en längre planka med hjälp av ett antal mindre brädor. Det finns tre olika typer av brädor, som har längden 1,2 respektive 3 meter. Det finns ett obegränsat antal av varje typ. Skriv ett program som bestämmer på hur många olika sätt man kan åstadkomma en planka av längden n,1 n 24. I figuren ser du de 7 olika sätten att skapa en planka med längden 4 meter. Dialogen från en programkörning: Plankans längd: 4 Det finns 7 olika sätt UPPGIFT 1.17 Lek med kulor Figur 1.10: En röd kan växlas till två vita och två blå. En vit kan växlas till tre blå och fyra röda. En blå kan växlas till tre vita och en röd. En lek med kulor för barn som kan räkna, står det på kartongen, som innehåller tillräckligt med kulor i tre olika färger: blå, vita och röda. Leken går ut på att till slut ha lika många kulor av varje färg. Hur många kulor man har från början varierar från omgång till omgång. Genom att lämna en kula till banken, kan de växlas mot ett antal kulor av de två andra färgerna enligt följande: En blå kan växlas mot 3 vita 1 röd En vit kan växlas mot 3 blå 4 röda En röd kan växlas mot 2 vita 2 blå Håkan Strömberg 21 KTH Syd

1.4. PROGRAMMERINGSUPPGIFTER Man får högst göra 15 växlingar för att nå målet och man ska nå det med så få växlingar som möjligt. Skriv ett program som frågar efter antalet kulor i de olika färgerna från start och som bestämmer det minsta antalet växlingar som behövs för att till slut få lika många kulor av varje färg. Indata: Programmet frågar efter antalet kulor i de tre färgerna. Hur många blåa? 22 Hur många vita? 22 Hur många röda? 13 Utdata: En rad, som talar om det minsta antalet växlingar som behöver göras för att målet Det behövs minst 6 växlingar för att nå målet Endast testexempel där det finns en lösning kommer att användas. UPPGIFT 1.18 Norska vargar. 3 När den norska regeringen väl bestämt sig för att minska vargstammen, beslöt man att 42 var det antal vargar man ville stanna vid efter avskjutningen. Jägarna fick nu följande regler att rätta sig efter, för att i olika etapper, från de ursprungliga n vargarna försöka nå fram till de 42 önskade. Om n är ett jämnt tal får i denna etapp n halveras. Om n är delbart med 3 och/eller 4, så får, i denna etapp, lika många vargar som produkten av de två sista siffrorna i n visar, skjutas bort. Om n är delbart med 5 får, om det är möjligt, 42 vargar skjutas bort i denna etapp. Skriv ett program som tar emot ett tal 42 < n < 10000, som anger hur många vargar det finns just nu, och som tar reda på om det är möjligt att nå det önskade antalet 42. Ett exempel förklarar. Vi börjar med 250 vargar 250 är delbart med 5 och > 42 så vi kan skjuta bort 42 vargar och få 208 kvar. 208 är ett jämnt tal så vi kan skjuta bort hälften och får 104 kvar. 104 är ett jämnt tal och vi kan åter skjuta bort hälften. Återstår nu 52 vargar. 52 är delbart med 4 och då kan vi skjuta bort 5 2 = 10 vargar och vi har nått fram till 42! De olika reglerna kan alltså användas i vilken ordning som helst. Observera dock att i exemplet ovan är visserligen 208 delbart med 4, men regeln kan inte tillämpas eftersom 0 8 = 0. Två körningsexempel: Hur många vargar finns nu? 7762 Målet kan INTE nås Hur många vargar finns nu? 7461 Målet kan nås 3 Denna uppgift har tidigare använts i kursen Algoritmer og Datastrukturer vid Universitet i Oslo men då handlade den om teddybjörnar. Håkan Strömberg 22 KTH Syd

Kapitel 2 Datastrukturer 2.1 Introduktion till datastrukturer Att studera datastrukturer är att lära sig om metoder för att organisera data så att de passar databearbetningen. Detta ingår som en klassisk del i datalogin. Å ena sidan har vi själva datorn hårdvaran med internt och externt minne. De elementära byggstenarna är bytes som man får tillgång till via heltalsadresser. Å andra sidan, när man skriver program, organiserar man data på en högre nivå, med hjälp av datastrukturer, i stället för att manipulera data på byte-nivå. En datastruktur kan ses som en container, skapad för att lagra en samling objekt av en given typ. Dessa objekt kallar vi fortsättningsvis element. Med hjälp av variabler (locators) kan vi lokalisera (hitta fram till) data i datastrukturen. När ett element lagras i en container är det en locator som gör det möjligt att senare nå detta element. Till varje datastruktur finns ett antal operationer. Operationer som till exempel lagrar nya element, tar reda på om ett visst element är representerat i containern eller returnerar antalet element i containern. Hur effektiv datastrukturen är avgörs av hur stor plats containern tar och hur lång tid operationerna behöver för att utföras. Till effektiviteten måste man nog också räkna hur lättanvänd strukturen är när man ska lösa praktiska problem. 2.1.1 Abstrakta datastrukturer Datastrukturer är konkreta implementationer av abstrakta datatyper(adt). Abstrakta datatyper är väl definierade datatyper med en tillhörande mängd operationer. En viss ADT kan implementeras (konkretiseras) på olika sätt. För en del av operationerna kan en viss implementation vara bättre (effektivare) än en annan. Står man inför ett större programmeringsprojekt bör man först välja vilka abstrakta datastrukturer man ska använda. Senare, under arbetet med att skriva programkoden kommer frågan hur de ska implementeras. 23

2.2. STACK 2.1.2 Grundläggande datastrukturer Det finns förstås en repertoar av återkommande och mer eller mindre standardiserade abstrakta datastrukturer. Den viktigaste kategorin är sekvens. Ofta vill man samla elementen i en följd, såsom i en lista eller tabell. Till denna hör lista, stack och kö. Några vanliga operationer är insättning, sökning och borttagning. Den vanligaste realiseringen av dessa datatyper är genom array eller länkad lista. Andra abstrakta datatyper, mer eller mindre vanliga är ordnat träd, binärt träd, lexikon, mängd, prioritetskö och graf. Även textbehandling, geometri och grafik kräver ibland sina egna datastrukturer som kanske inte direkt tillhör någon av de ovan uppräknade. Stack 2.2 Stack I figur 2.1 ser du den vanligaste metaforen för en stack en tallrikshållare på en lunchrestaurang. Bilden vill visa att: den tallrik vi lägger dit sist också är den vi först plockar bort. En stack är en LIFO, last in first out eller hellre på svenska SIFU, sist in först ut Figur 2.1: En tallriksstack En programmerare sitter vid sin dator när telefonen ringer. I samma ögonblick som han svarar läggar han arbetet vid datorn på en stack. Detta arbete får vila till telefonsamtalet är över. Under tiden som samtalet pågår kommer ett nytt samtal. Han ber personen från det första samtalet att vänta ett stund och kopplar in nästa. Han lägger det första telefonsamtalet på stacken och just nu har vår programmerare två jobb på stacken. Precis då ställer sig en av hans kollegor i dörren och undrar om han ska med på lunch. För en kort stund hamnar alltså även det andra samtalet på stacken när han svarar sin kollega: Ja, jag kommer snart. Han återgår sedan till det andra samtalet. Han hämtar ned det från stacken. När han avslutar det samtalet hämtar han så ned det första från stacken. När även det är avslutat finns bara programmeringsjobbet kvar. Han blir påmind om lunchen. Ett jobb blir kvar på stacken i en timme till... 2.2.1 Operationer Vi har tidigare sagt att till en abstrakt datastruktur finns ett antal operationer. Först till de som är viktigast för en stack: Håkan Strömberg 24 KTH Syd

KAPITEL 2. DATASTRUKTURER STACK Push. Push lägger ett element överst på stacken. Denna operation är förstås alldeles nödvändig för en stack. För att vara säker på att man inte överskrider stackens kapacitet testar operationen så att stacken inte är full. Om stacken redan är full vet vi inte hur vi ska fortsätta och avbryter därför exekveringen. Det är svårt att välja rätt nivå när vi ska beskriva dessa operationer. Först den högsta tänkbara nivån. Algorithm 2.2.1: PUSH(element) Lägg element överst på stacken Följande nivå är mer detaljerad och kanske kommer man för nära själva implementationen. Man känner på sig att Stack här är en array och StackMax är en övre gräns för hur många element stacken kan innehålla. Eftersom det finns andra sätt att konkretisera denna operation är denna nivå för låg. Algorithm 2.2.2: PUSH(element) if StackIndex { < StackMax StackIndex StackIndex+1 then { Stack[StackIndex] = element Felmeddelande else avbryt Pop. Pop returnerar det översta elementet på stacken och tar samtidigt bort det från stacken. Under förutsättning att stacken inte är tom förstås. Även denna operation är också nödvändig för en normal stack. Om denna operation försöker utföras på en tom stack vet vi inte hur vi ska fortsätta och avbryter därför processen med ett felmeddelande. Algorithm 2.2.3: POP() Returnerar det översta elementet på stacken IsEmpty. IsEmpty returnerar true om stacken är tom och false annars. Operationen är till för att klienten ska ha möjlighet att undvika att anropa Pop när stacken är tom, vilket skulle leda till att exekveringen avbryts. Håkan Strömberg 25 KTH Syd

2.2. STACK Algorithm 2.2.4: ISEMPTY() if Stacken är tom then return (TRUE) else return (FALSE) IsFull. IsFull returnerar true om stacken är full och false annars. Denna operation är på liknande sätt som IsEmpty till för att undvika plötsliga avbrott. Är stacken redan full ska man inte anropa Push Algorithm 2.2.5: ISFULL() if Stacken är full then return (TRUE) else return (FALSE) Här följer några mindre viktiga operationer som möjligtvis kan förekomma i samband med en stack. Top Top returnerar första värdet på stacken utan att ta bort det. Någon gång kanske man bara vill testa översta elementet på stacken, utan att ta bort det. Den här operationen kan man klara sig utan eftersom en Pop, följt av en Push av elementet igen, ger samma resultat. InitStack InitStack initierar stacken innan vi börjar använda den. För vissa implementationer, speciellt de som använder dynamiskt minne måste stacken först initieras (skapas). FreeStack Använder vi InitStack för att skapa vår stack så måste vi ha en operation, FreeStack, för att ta bort den från heapen när den inte ska användas längre. StackDepth För att få reda på hur många element som just nu finns på stacken. IsOnStack En operation som tar reda på om ett givet element finns på stacken. Operationen returnerar 1 om elementet inte finns. Annars returnerar funktionen platsen där elementet finns. Stacken lämnas orörd. Från valet av operationer ovan kan diskussionen om vem som har ansvaret väckas. Ska operationerna omsättas till kod på ett sätt som gör klienten fri från ansvar. Det vill säga ska rutinerna som sköter stacken hålla reda på hur många element som finns där, se till att programmet inte kraschar genom tester i Pop och Push. Om klienten i stället håller reda på hur många element stacken innehåller så finns ingen risk för felaktiga anrop. 2.2.2 Implementation av en stack Vi har nu definierat den abstrakta datastrukturen stack. Hur kan den då implementeras? Hur ska vi realisera den i vår programkod. Vi börjar med ett exempel: Håkan Strömberg 26 KTH Syd

KAPITEL 2. DATASTRUKTURER STACK EXEMPEL 2.1 I denna uppgift ska du skriva ett program som kan analysera parentesuttryck. Programmet ska avgöra om den inmatade strängen med parenteser är balanserad och korrekt. Strängen kan endast innehålla följande tecken ( ) { } [ ] Att ett parentesuttryck är korrekt betyder att det innehåller lika många vänster- som högerparenteser av varje sort och att parenteserna dessutom kommer i rätt ordning. Nedan följer några exempel (()) Korrekt (())) Felaktigt [{()}] Korrekt [[[[[ Felaktigt [[[((()))]]] Korrekt [({}]) Felaktigt (([{}]))) Felaktigt (((])) Felaktigt Den inmatade strängen får högst innehålla 30 parenteser. En mycket passande algoritm för att lösa problemet Algorithm 2.2.6: PARENTESTEST(s) INITSTACK() for i 1 to LENGTH(s) if vänsterparentes then PUSH(s(i)) if högerparentes do if ISEMPTY() then return (felaktig) then if POP() not matchar s[i] then return (felaktig) if ISEMPTY() then return (korrekt) else return (felaktig) Vi har använt oss av operationerna PUSH, POP, INITSTACK och ISEMPTY. Om vi väljer en array som stack, en array som kan lagra tecken (char) så kommer motsvarande funktioner att se ut så här. Håkan Strömberg 27 KTH Syd

2.2. STACK 1 char stack[30]; 2 int plats=0; 3 4 void push(char t){ 5 stack[plats]=t; 6 plats++; 7 } 8 9 char pop(void){ 10 char t1; 11 plats ; 12 t1=stack[plats]; 13 return t1; 14 } 15 16 bool isempty(void){ 17 return (plats<0); 18 } 1 2 Vi tillåter oss två globala variabler här, själva stacken stack och plats som håller reda på var nästa lediga plats finns för ett stackat element. 4 7 Lägger in det pushade elementet i stack och ökar plats. 9 14 Minskar platsen, tar ut ett element från stacken, och returnerar elementet. 16 18 Returnerar true om plats är negativ, det vill säga stacken är tom. H Vad är det som gör att man med dessa funktioner inte kan testa två parentesuttryck i samma programkörning? Med vilken tidigare nämnd operation kan man avhjälpa detta? Det återstår en del jobb för att hålla reda på vad som är vänsterparentes, vad som är högerparentes och när de matchar. Men de lämnar vi. För den intresserade heter programmet parentes.c 2.2.3 Stackbibliotek Även om dessa funktioner är korta och enkla att återskapa från minnet så är det otillfredsställande att skriva om dem varje gång man ska använda dem. Målet är därför att placera dem i en egen fil stack.c och att skapa en stack.h som kan inkluderas i början av klientprogrammet på vanligt sätt. Här följer ett enkelt, första försök. Vi visar delar av de tre filerna stack.h, stack.c och stacktest.c 1 void push(char t); 2 char pop(void); 3 int isempty(void); 4 int isfull(void); 1 3 Ovan ser vi stack.h, som i detta enkla fall inte innehåller något annat än funktionsdeklarationer för fyra stackoperationer. Håkan Strömberg 28 KTH Syd

KAPITEL 2. DATASTRUKTURER STACK 1 static char stack[30]; 2 static int plats=0; 3 4 void push(char t){ 5 stack[plats]=t; 6 plats++; 7 } 8... 1 2 Själva stacken deklareras tillsammans med variabeln plats som ska hålla reda på antalet. Båda dessa variabler deklareras som static vilket innebär att de inte är globala men inte kända utanför denna modul. 4 8 Sedan kommer funktionerna push, pop, isempty och isfull i en följd. 1 #include "stack.h" 2 #include <stdio.h> 3 void main(void){ 4... 5 push( h ); 6 c=pop(); 7... 8 } 1 Lönen för vår möda det räcker nu att skriva #include "stack.h" för att nå biblioteket stack 5 6 Länkaren hittar koden till push och pop i den obj-fil som skapas då stack.c kompileras. Så här långt fungerar det hela om vi utvecklar programmet i ett projekt där dessa tre filer ingår. UPPGIFT 2.1 Implementera enkel stack. På hemsidan hittar du en zip-fil med länken Enkel Stack (array), som innehåller filerna main.c, stack.h och stack.c. Skapa ett projekt som kan exekvera main.c. 2.2.4 Ännu generellare stack I det stackbibliotek vi skrivit ovan kan elementen bara vara av typen char. Om vi vill använda en stack där elementen är av typ int eller kanske en av klienten definierad post, struct, så måste vi med den teknik vi hittills använt, skriva en uppsättning funktioner för varje datatyp. Om typen hos elementet är en post så kan vi ju aldrig förutsäga hur den kommer att se ut och måste därför skriva stack.h och stack.c för varje tillämpning. Håkan Strömberg 29 KTH Syd

2.2. STACK 2.2.5 Flera stackar samtidigt Det är tänkbart att vi råkar ut för en situation där vi vill ha flera stackar igång samtidigt. Kan detta lösas med den kod vi skrivit ovan? Nej och det är uppenbart att när vi anpassar rutinerna till detta fall, så måste vi i alla funktioner lägga till en ny parameter, som anger vilken stack som avses. Det vi saknar här kan lösas med objektorienterad programmering i Java. UPPGIFT 2.2 Implementera stack i Java. I Java finns förstås redan stack implementerad. Trots det ska du här skriva en generell klass Stack med flera av de operationer vi nämnt ovan. På filen namn.dat finns ett antal förnamn och vilken fotbollsklubb de hejar på AIK eller DIF. Filen inleds med ett tal n som anger hur många personer den innehåller. Därefter följer 2n rader, först förnamnet på en rad sedan klubben på nästa. Använd den nyskrivna Java-klassen för att åstadkomma en utskrift där först namnen på alla AIK-are skrivs ut, följt av djurgårdarnas namn. 2.2.6 Stacken som en länkad lista. 1 struct objekt{ 2 int nummer; 3 struct objekt naesta; 4 }; 5 6 typedef struct objekt objekt 7 8 objekt start=null; 1 4 Vår första stack ska hantera heltal. Varje element i stacken skall vara av typen objekt. Strukturen objekt innehåller, i tillägg till heltalet, en pekare till nästa elementet på stacken. 6 Pekaren start håller reda på var stackens topp finns. Den pekar ut det sist pålagda elementet. Om start är NULL betyder det att stacken är tom. 1 bool tom(void){ 2 return start==null; 3 } Funktionen tom returnerar true om stacken är tom och false om det finns minst ett element. Ovan har vi ju sagt att stacken är tom om start är NULL. Håkan Strömberg 30 KTH Syd

KAPITEL 2. DATASTRUKTURER STACK 1 void push(int nr){ 2 objekt ny; 3 ny=(objekt )malloc(sizeof(objekt)); 4 ny >nummer = nr; 5 ny >naesta=start; 6 start=ny; 7 } Med hjälp av denna funktion lägger vi till element på stacken. I vardagslag använder man de svengelska ordet pusha (skjuta på) för att lägga upp ett element på en stack. Ordet är just nu ett modeord det är ofta man pushar för olika saker. Funktionen tar emot ett heltal, men returnerar ingenting. Man räknar helt enkelt med att det kommer att lyckas och har därför ingen anledning att rapportera. Det enda som skulle kunna gå snett är att stacken inte rymmer det nya elementet. Misstänker man att detta kan inträffa är nog det bästa att ha en global variabel som noterar denna händelse. Detta för att hålla stackfunktionerna så rena som möjligt från ovidkommande parametrar. 2 4 ny är en pekare till ett objekt. Med malloc allokerar vi dynamiskt ett utrymme på heapen. Sen kopieras värdet på variabeln nr till nummer. 5 8 Detta element ska nu hamna överst i stacken. Om stacken är tom innan, har detta enda element ingen efterföljare. Pekaren naesta får då värdet NULL genom att sättas lika med start pekaren som är NULL om stacken är tom. I annat fall kommer det tidigare första elementet att sjunka ner i stacken och det nya elementet har en efterföljare som tidigare pekades ut av start. 6 Till sist justeras start, som ju ska peka ut första elementet. 1 int pop(void){ 2 int nr; 3 nr = start >nummer; 4 objekt p = start; 5 start = start >naesta; 6 free(p); 7 return nr; 8 } Funktionen pop hämtar ner element från stacken. Inga indata och ett heltal som utdata. 3 4 Funktionen tar i variabeln nr hand om värdet på heltalet till det första elementet på stacken. Pekaren p sätts till att peka på det första elementet på stacken. Denna pekare skall vi använda för att deallokera det minne som vi tidigare allokerade dynamiskt. 5 Det första elementet på stacken länkas sen ut ur listan genom att flytta startpekaren så den pekar på nästa elementet på stacken. 6 Utrymmet som vi allokerade dynamiskt med hjälp av malloc friges sen med Håkan Strömberg 31 KTH Syd

2.2. STACK hjälp av free. Vi använder här pekaren p som vi satt att peka på det första elementet på stacken. 7 Till sist returneras värdet på heltalet till det element som vi har tagit bort från stacken. Om elementet är det sista på stacken kommer start att få värdet NULL i annat fall kommer start att peka ut elementets efterföljare. Programmeraren får själv förvissa sig om att stacken inte är tom, före anropet av pop, med hjälp av tom. EXEMPEL 2.2 Ett program som utnyttjar de tre funktionerna för att bygga upp stacken, så som den ser ut i figurerna. Figur 2.2: Push anropas med ett element vars värde är 96. Så här ser stacken ut efter första anropet 1 void main(void){ 2 int nr; 3 nr=96; push(nr); Figur 2.3: Nu står ett element med värdet 48 på tur att stackas Figur 2.4: Efter push(a) har talet 48 hamnat på stacken. 1 nr=48; push(nr); 1 nr=7; push(nr); Håkan Strömberg 32 KTH Syd

KAPITEL 2. DATASTRUKTURER STACK Figur 2.5: På samma sätt hamnar där sedan ett element med värdet 7 Figur 2.6: Med hjälp av pop kan vi nu lyfta ned det översta elementet från stacken, det med värdet 7 1 while(!tom()){ 2 nr=pop(); 3 printf("%d",nr) 4 } 5 } Observera att vi i huvudprogrammet endast talar om vilka heltal vi vill lägga på stacken. Sen tar programmet själv hand om att skapa element av typen objekt som lägges på stacken. 1 4 I denna loop poppas ett element i taget av från stacken till stacken blir tom. Håkan Strömberg 33 KTH Syd

2.3. IDIOTEN UPPGIFT 2.3 Implementera enkel stack som länkad lista. På hemsidan hittar du en fil med länken Enkel Stack (länkad lista), som innehåller filerna main.c, stack.h och stack.c. Skapa ett projekt som kan exekvera main.c. Skapa gärna en roligare tillämpning. UPPGIFT 2.4 Sortering med hjälp av stackar. På hemsidan hittar du en fil med länken StackSort, som innehåller filerna main.c, stack.h, stack.c och tal.dat. Skapa ett projekt som kan exekvera main.c. Sätt dig in i koden så att du kan redogöra för hur programmet fungerar. 2.3 Idioten Många patienser brukar kallas Idioten, antagligen för att de på ett eller annat sätt gör skäl för namnet. Det här är emellertid den riktiga Idioten, vilket dock inte på något sätt innebär att den är dummare än alla andra. Tvärtom, det är en trevlig patiens med fler kombinationsmöjligheter än man kanske från början kan upptäcka. I Dostojevskis berömda roman Bröderna Karamasov lägger Grusjenka en patiens som kallas Idioten. Kanske var det den här. Figur 2.7: 2 och 9 kan kastas. Därefter kan E flyttas ner. Då blir 7 fri och 3 kan kastas, varefter 10 kan flyttas till en tom hög 2.3.1 Så går den till En vanlig kortlek med 52 kort. Man börjar med att lägga ut fyra kort med framsidan upp i en vågrät rad (se figur 12.1). Finns bland dessa fyra kort två eller flera av samma färg, har man rätt att plocka bort de lägsta korten i denna färg. När det inte går att plocka bort fler kort, lägger man ut en ny rad med fyra kort ovanpå de förra. Esset är högsta kortet. Om några av de översta korten är av samma färg, får man återigen plocka bort de lägsta, tills det bara ligger olikfärgade kort kvar. Var gång det uppstår ett tomrum, får man lägga dit vilket som helst av de översta korten från de andra högarna. På det sättet fortsätter man att lägga ut rader om fyra kort i taget, kastar de lägre i samma färg och flyttar kort till tomma platser. Patiensen har gått ut, när endast de fyra essen ligger kvar på bordet. På hemsidan finns programmet Patiens demo, med vilket du kan träna tills du förstår vad den går ut på. Du lägger ut nya kort genom att klicka på knappen Lägg ut, tar bort ett kort med vänster musknapp och flyttar ett kort till en tom plats genom att använda höger musknapp två gånger. Håkan Strömberg 34 KTH Syd